{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# Numpy基础介绍\n",
    "    NumPy（Numerical Python）是一个开源Python科学计算库，是Python生态圈最重要的底层支持库，支持快速的数组运算，比纯Python代码快得多。\n",
    "    \n",
    "    NumPy是在Python中进行数据分析、机器学习、人工智能开发的必备工具，是理解学习很多Python工具包的基础。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#csb_print_codes函数总结\r\n",
    "np.arange(1,10) #创建[0,1,2,3,4,5,6,7,8,9]的一维数组，要想变换形状，np.arange(1,10).reshape(3,4) 3行4列,reshape(3,-1)只需考虑行，列自动计算\r\n",
    "np.linspace(1,2,5) #创建1-2之间的五个数，[1.   1.25 1.5  1.75 2.  ],只能是一维数组，不能在函数中加入行列\r\n",
    "np.set_printoptions(precision=2) #控制输出精度，保留两位小数\r\n",
    "np.random.randn(3,4) #生成3*4标准正态分布的小数,randn()是标准正态分布，不需要指定范围，因此只需要指定行列\r\n",
    "np.random.randint(1,10,(2,5)) #生成[1,10)，2行5列的矩阵\r\n",
    "np.repeat([1,2],2) #重复函数[1,1,2,2]\r\n",
    "np.tile([1,2],2) #重复函数[1,2,1,2]\r\n",
    "np.ones((3,1))\r\n",
    "np.zeros((3,4))\r\n",
    "np.ones_like(a)\r\n",
    "np.zeros_like(a)\r\n",
    "np.eye(4,k=1) #eye()必须是方阵\r\n",
    "np.full((3,3),\"csb\") #全填充矩阵函数\r\n",
    "np.where(a>98, a ,\" \") #np.where(condition, ,else)符合条件输出，不符合输出\r\n",
    "np.sort(b) #排序方法，按从小到大，axis = 0,沿轴的方向排序\r\n",
    "np.sort(-b) #排序方法，按从大到小\r\n",
    "np.argsort(b) #排序方法，按从小到大，但返回的是之前的位置标号\r\n",
    "np.median(b)  #方差、标准差、中位数\r\n",
    "b.argmin() \t#最大值、最小值所对应的索引下标\r\n",
    "np.prod(c)\t#累乘\r\n",
    "np.cumprod(c) #累乘并给出中间结果 \r\n",
    "np.cumsum(c) #累加并给出中间结果\r\n",
    "np.unique(b) #返回不重复的元素值 \r\n",
    "np.all(b) #b所有元素都为非0值则返回\r\n",
    "np.any(b) #b有任意元素为非0值则返回\r\n",
    "np.prod(c) #累乘  \r\n",
    "np.cumprod(c) #累乘并给出中间结果  \r\n",
    "np.cumsum(c) #累加并给出中间结果\r\n",
    "np.unique(b) #返回不重复的元素值 \r\n",
    "np.all(b) #b所有元素都为非0值则返回\r\n",
    "np.any(b)  #b有任意元素为非0值则返回\r\n",
    "np.isnan(b)  \t#isnan()测试是否nan值\r\n",
    "\r\n",
    "b.shape\r\n",
    "b.ndim\r\n",
    "b.size\r\n",
    "\r\n",
    "b.max()\r\n",
    "b.min()\r\n",
    "b.ptp() #极差\r\n",
    "b.sum()\r\n",
    "b.mean()\r\n",
    "b.var()\r\n",
    "b.std()\r\n",
    "b.argmax()\r\n",
    "b.argmin() \t# 最大值、最小值所对应的索引下标\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 2 创建Numpy数组\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 3 5 7 9]\n",
      "8\n",
      "----------\n",
      "[1 2 3]\n",
      "----------\n",
      "[1.   1.25 1.5  1.75 2.  ]\n",
      "[1.   1.25 1.5  1.75 2.  ]\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "a = np.arange(1,10,2)\r\n",
    "print(a)\r\n",
    "print(a.ptp()) \r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "b = np.array((1,2,3)) #通过元组进行创建array数组\r\n",
    "print(b)\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "c = np.linspace(1,2,5) #返回1-2之间均匀分布的五个数\r\n",
    "print(c)\r\n",
    "print(np.array(c))\r\n",
    "print(c.size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a=[0 1 2 3 4 5 6 7 8 9]\n",
      "b=[20 50 30]\n",
      "通过元组创建array数组\n",
      " tup2ar=[0 1 2 3 4]\n",
      "通过列表创建数组\n",
      " lst=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
      " lst2np=[0 1 2 3 4 5 6 7 8 9]\n",
      " lst3=[0 1 2 3 4 5 6 7 8 9]\n",
      "\n",
      "x=[1.   1.25 1.5  1.75 2.  ]\n",
      "y=[0.         0.52359878 1.04719755 1.57079633 2.0943951  2.61799388\n",
      " 3.14159265]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "\n",
    "import numpy as np\n",
    "\n",
    "a = np.arange(10) #  通过arange\n",
    "b = np.array((20,50,30)) # array \n",
    "print(\"a={}\\nb={}\".format(a,b))\n",
    "\n",
    "# 通过元组创建array数组\n",
    "tup = tuple(range(5))\n",
    "tup2arr = np.array(tup)\n",
    "print(\"通过元组创建array数组\\n tup2ar={}\".format(tup2arr))\n",
    "\n",
    "# 通过列表创建数组\n",
    "lst = list(range(10)) # 创建一个list列表\n",
    "lst2np = np.array(lst)\n",
    "lst3 = np.array(list(range(10)))\n",
    "print(\"通过列表创建数组\\n lst={}\\n lst2np={}\\n lst3={}\\n\".format(lst,lst2np,lst3))\n",
    "‘\n",
    "x = np.linspace(1, 2, 5)  # 均匀分布\n",
    "y = np.linspace(0, np.pi, 7)  \n",
    "print(\"x={}\\ny={}\\n\".format(x,y))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.84 0.41 0.15 0.73]\n",
      " [0.52 0.67 0.95 0.3 ]\n",
      " [0.99 0.68 0.03 0.11]]\n",
      "----------\n",
      "[[-0.9  -0.35  1.01 -0.53]\n",
      " [ 0.61 -0.89 -0.37 -1.24]\n",
      " [-0.75  2.33 -0.37  0.45]]\n",
      "----------\n",
      "[[57 49  4 87]\n",
      " [92 97 86 20]\n",
      " [49  3 11 24]]\n",
      "----------\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "np.set_printoptions(precision = 2) #控制输出精度\r\n",
    "a = np.random.rand(3,4) #生成3*4的0-1均匀分布的小数\r\n",
    "print(a)\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "b = np.random.randn(3,4) #生成3*4标准正态分布的小数\r\n",
    "print(b)\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "c = np.random.randint(1,100,(3,4)) #生成3*4 1-100的随机整数\r\n",
    "print(c)\r\n",
    "print(\"-\"*10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ar=[[0.02 0.62 0.28 0.98]\n",
      " [0.08 0.92 0.71 0.07]\n",
      " [0.64 0.26 0.37 0.51]]\n",
      "ar2=[-1.    0.33  0.86  0.33 -0.81 -0.94  0.34 -0.5   1.7  -0.41]\n",
      "a=[79 86 15 21 92]\n",
      "b=[[85 69  4]\n",
      " [60 27  3]]\n",
      "c=[[ 2 21 59]\n",
      " [ 6 93  5]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 使用numpy中的随机函数生成数组\n",
    "## 生成【0，1】间的随机数\n",
    "np.set_printoptions(precision=2)   # 控制输出的精度\n",
    "ar = np.random.rand(3,4)  # 3x4数组, [0, 1)内均匀分布小数\n",
    "ar2 = np.random.randn(10) # 生成10个（0，1）正态分布的数\n",
    "print(\"ar={}\\nar2={}\".format(ar,ar2))\n",
    "## 生成特定范围内的随机整数\n",
    "a = np.random.randint(1,100,5)  \t\t# [1,100)内5个随机整数\n",
    "b = np.random.randint(1,100,(2,3))  \t# 2x3 数组\n",
    "\n",
    "tup = tuple((2,3))\n",
    "c = np.random.randint(1,100,tup)\n",
    "print(\"a={}\\nb={}\\nc={}\\n\".format(a,b,c))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 1]\n",
      " [2 3]\n",
      " [4 5]]\n",
      "----------\n",
      "[[98 48 37 27]\n",
      " [89 63 56 30]\n",
      " [32  1 84 66]\n",
      " [57 54 44  3]\n",
      " [93 23 35 86]\n",
      " [62 57 73 88]\n",
      " [77 83 25 77]\n",
      " [86  6  3 78]\n",
      " [92 82 23  1]\n",
      " [79 70 34 96]\n",
      " [99 36 93 91]\n",
      " [56 12 34 69]\n",
      " [ 6 82 37 18]\n",
      " [91 20 94 64]\n",
      " [90 27 61 69]\n",
      " [32 53  8 25]\n",
      " [42 22  2 75]\n",
      " [90 63 87 72]\n",
      " [71 88 31 33]\n",
      " [54 56 12 80]\n",
      " [33 70 73 95]\n",
      " [99 33 66 65]\n",
      " [13 96 19 70]\n",
      " [ 9 34 10 87]\n",
      " [57  9 87 34]]\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "a = np.arange(0,6).reshape(3,-1)\r\n",
    "print(a)\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "b = np.random.randint(1, 100, 100).reshape(25,-1)\r\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=[[0 1 2]\n",
      " [3 4 5]]\n",
      "\n",
      "c=[[0 1 2]\n",
      " [3 4 5]]\n"
     ]
    }
   ],
   "source": [
    "# 创建二维数组\n",
    "b = np.arange(6).reshape(2, 3)\n",
    "c =np.arange(6).reshape(2,-1)  # 注意reshape函数中-1的用法。不用再计算列数。\n",
    "print(\"b={}\\n\\nc={}\".format(b,c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**注意** \n",
    "\n",
    "请大家补充np.arange() 、np.repeat、np.tile与 np.reshape()函数的具体用法尤其注意区分repeat and title 的区别\n",
    "\n",
    "尤其注意区分repeat and title 的区别\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2 4 6 8]\n",
      "----------\n",
      "[2 4 6 8 2 4 6 8 2 4 6 8]\n",
      "[2 2 2 4 4 4 6 6 6 8 8 8]\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "a = np.arange(2,10,2)\r\n",
    "print(a)\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(np.tile(a,3)) #整个的重复3遍\r\n",
    "print(np.repeat(a,3)) #单个重复3遍"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a=[1.  1.5 2.  2.5 3.  3.5 4.  4.5]\n",
      "b=[1.  1.  1.5 1.5 2.  2.  2.5 2.5 3.  3.  3.5 3.5 4.  4.  4.5 4.5]\n",
      "c=[1.  1.5 2.  2.5 3.  3.5 4.  4.5 1.  1.5 2.  2.5 3.  3.5 4.  4.5]\n"
     ]
    }
   ],
   "source": [
    "a = np.arange(1,5,0.5) \n",
    "b = np.repeat(a,2)    \n",
    "c = np.tile(a,2)   \n",
    "print(\"a={}\\nb={}\\nc={}\".format(a,b,c))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "38.3 µs ± 151 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n",
      "1.44 µs ± 47.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n"
     ]
    }
   ],
   "source": [
    "# 比较列表和array的运算速度\n",
    "N = 1000\n",
    "lst = list(range(N)) # 创建一个list列表\n",
    "%timeit lst2 = [x*2 for x in lst]\n",
    "arr = np.arange(N)\n",
    "%timeit arr2 = arr*2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "----------\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "----------\n",
      "[[0. 0. 0. 0.]\n",
      " [0. 0. 0. 0.]\n",
      " [0. 0. 0. 0.]]\n",
      "----------\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "----------\n",
      "[[0. 1. 0. 0.]\n",
      " [0. 0. 1. 0.]\n",
      " [0. 0. 0. 1.]\n",
      " [0. 0. 0. 0.]]\n",
      "----------\n",
      "2\n",
      "12\n",
      "(3, 4)\n",
      "[[0. 0. 0. 0. 0. 0.]\n",
      " [0. 0. 0. 0. 0. 0.]]\n"
     ]
    }
   ],
   "source": [
    "#csb_print_code\r\n",
    "a = np.ones((3,4))\r\n",
    "b = np.ones((3,4))\r\n",
    "c = np.zeros((3,4))\r\n",
    "print(a)\r\n",
    "print(\"-\"*10)\r\n",
    "print(b)\r\n",
    "print(\"-\"*10)\r\n",
    "print(c)\r\n",
    "print(\"-\"*10)\r\n",
    "print(np.ones_like(c))\r\n",
    "print(\"-\"*10)\r\n",
    "print(np.eye(4, k = 1))\r\n",
    "print(\"-\"*10)\r\n",
    "print(c.ndim)\r\n",
    "print(c.size)\r\n",
    "print(c.shape)\r\n",
    "print(c.reshape(2,6))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a=[1. 1. 1. 1. 1.]\n",
      "b=[[1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "c=[[0. 0. 0.]\n",
      " [0. 0. 0.]]\n",
      "d=[[1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "e=[[1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [0. 0. 1.]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 一些特殊的数组\n",
    "a = np.ones(5) \t\t# 生成含有5个元素的一维全1数组\n",
    "b = np.ones((2, 3))    \t# 生成2x3的二维全1数组\n",
    "c = np.zeros((2, 3)) \t# 生成2x3的二维全0数组\n",
    "d = np.ones_like(b)      \t# 生成维数和b相同的全1数组\n",
    "e = np.eye(3) \t\t# 单位矩阵数组\n",
    "print(\"a={}\\nb={}\\nc={}\\nd={}\\ne={}\\n\".format(a,b,c,d,e))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 数组对象特性 \n",
    "b = np.arange(6).reshape(2, 3) 数组b的具体属性如下图所示。\n",
    "\n",
    "![](images/n1.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 3 Numpy的常见数据类型\n",
    "\n",
    "![](images/n2.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['csb' 'csb' 'csb']\n",
      " ['csb' 'csb' 'csb']\n",
      " ['csb' 'csb' 'csb']]\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "a = np.full((3,3),\"csb\") #全填充矩阵函数\r\n",
    "print(a)\r\n",
    "print(\"-\"*10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=[0. 1. 2. 3. 4.] type of b is float64 \n",
      "\n",
      "c=[0 1 2 3 4] type of c is int64 \n",
      "\n"
     ]
    }
   ],
   "source": [
    "a = np.full((3,3),True) # 生成逻辑数据类型\r\n",
    "\r\n",
    "b=np.arange(5, dtype='float')  #  指定为 'float'\r\n",
    "print(\"b={} type of b is {} \\n\".format(b,b.dtype))\r\n",
    "\r\n",
    "c=b.astype('int')             \t# 转为int，b不变，得到新的c\r\n",
    "print(\"c={} type of c is {} \\n\".format(c,c.dtype))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 2 数组的存取\n",
    "\n",
    "np数组可按类似列表访问的语法，单个索引或切片访问。使用形式类似matlab语言。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "c=\n",
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n",
      "----------\n",
      "5\n",
      "----------\n",
      "[0 5]\n",
      "----------\n",
      "[[ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n",
      "----------\n",
      "[[ 5  6  7]\n",
      " [ 9 10 11]]\n",
      "----------\n",
      "6\n",
      "----------\n",
      "[ 4  5  6  7  8  9 10 11]\n",
      "----------\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "c = np.arange(0,12).reshape(3,4)\r\n",
    "print(\"c=\\n{}\".format(c))\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[1,1])\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[[0,1],[0,1]])\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[1:]) #没有逗号后面的，默认是行数\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[1:3,1:4]) \r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[1][2]) \r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(c[c>3]) #返回c中大于3的数，变成一维列表输出\r\n",
    "print(\"-\"*10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=[5 6 7 8 9]\n",
      "b[1]=6\n",
      "b[-1]=9\n",
      "b[:2]=[5 6]\n",
      "b[1:3]=[6 7]\n"
     ]
    }
   ],
   "source": [
    "b=np.arange(5,10)    \t\t# array([5,6,7,8,9])\r\n",
    "b[1],  b[-1],    b[:2], b[1:3]      \t# (6, 9, array([5, 6]), array([6, 7]))\r\n",
    "b[[0,1,3]]   \t\t\t# 花式索引 array([5,6,8]),  复制\r\n",
    "print(\"b={}\\nb[1]={}\\nb[-1]={}\\nb[:2]={}\\nb[1:3]={}\".format(b,b[1],b[-1],b[:2],b[1:3]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**重要**:数组切片和列表切片不同，前者是视图，后者是复制。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "id_c=140296272677824\n",
      "id_b=140296272677824\n",
      "使用copy复制后：\n",
      "id_c=140296272677584\n",
      "id_b=140296272677824\n"
     ]
    }
   ],
   "source": [
    "b=np.arange(5,10)    \t# array([5,6,7,8,9])\r\n",
    "c=b       \t\t# 产生视图，b,c指向同一内存块\r\n",
    "c[0]=100     \t\t# 此修改将同时影响 b 和 c\r\n",
    "print(\"id_c={}\\nid_b={}\".format(id(c),id(b)))  \t\t\t# array([100,6,7])\r\n",
    "\r\n",
    "# 使用下面的方式\r\n",
    "c = b.copy()  \r\n",
    "print(\"使用copy复制后：\\nid_c={}\\nid_b={}\".format(id(c),id(b)))  \t\t\t# array([100,6,7])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**二维数组的索引操作**\n",
    "\n",
    "二维数组分行、列两个维度，用[行,列]、[行]、[:,列]访问"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]]\n"
     ]
    }
   ],
   "source": [
    "b=np.arange(12).reshape(3,4)\r\n",
    "print(b)\r\n",
    "\r\n",
    "# 尝试输出下面的索引，观察体会索引方式。\r\n",
    "b[ [0,1,2], [3,2,1] ]  # array([3, 6, 9]),  将行/列坐标分别排列，花式索引\r\n",
    "b[1, 2]  \t\t# 6，也可写为  b[1][2] \r\n",
    "b[1, 1:3]    \t# array([5,6])\r\n",
    "b[1]   \t\t# array([4,5,6,7])\r\n",
    "b[:,1]          \t# array([1,5,9])\r\n",
    "b[1:3, 1:4]   \t# array([5,6,7], [9,10,11])\r\n",
    "b[1][2]=b[1,2]\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 布尔索引 \n",
    "\n",
    "筛选出True值对应位置的数据。该方法主要用来筛选矩阵中的元素，很多场景都能用到。\n",
    "\n",
    "**找出特定的元素**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b中小于60的元素为[44 43 59]\n",
      "b中[60,80]间的元素=[65 63 79 68]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 筛选出向量中小于给定值的元素\r\n",
    "import numpy as np\r\n",
    "np.random.seed(7) #相同的种子可确保随机数按序生成时是相同的，结果可重现\r\n",
    "b = np.random.randint(40, 100, size=10) \t# 生成10个的随机整数\r\n",
    "c = b < 60\r\n",
    "# print('b={}\\nb中小于60的元素为{}'.format(b,b[c]))      # 此处报错\r\n",
    "print('b中小于60的元素为{}'.format(b[b<60])) # another way\r\n",
    "\r\n",
    "# 当然，还可以使用更为复杂的表达方式。\r\n",
    "print(\"b中[60,80]间的元素={}\\n\".format(b[(b>=60) & (b<=80)]))  \t# 显示60~80间的数据, 此处用&, 不能用and\r\n",
    "# b[(b<60) | (b>90)]        \t# 显示<60  或 >90的数据,此处用|, 不能用or\r\n",
    "# b[~(b<60)]\t\t# ~非, 显示 >=60的数据)\r\n",
    "\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**找出特定元素的位置**\n",
    "\n",
    "r = np.where(condition,x,y),自行查找和体会where的用法。\n",
    "\n",
    "该函数功能非常强大，一定要掌握。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '\n",
      " ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '\n",
      " ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '\n",
      " ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '\n",
      " ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '\n",
      " ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '99' '100']\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "a = np.arange(1,101)\r\n",
    "print(np.where(a>98, a ,\" \")) #np.where(condition, ,else)符合条件输出，不符合输出"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=[[87 44 65 94 43 59]\n",
      " [63 79 68 97 54 63]\n",
      " [48 65 86 82 66 48]\n",
      " [79 78 44 88 47 84]\n",
      " [40 51 95 98 46 59]]\n",
      "b中小于70的元素为\n",
      "\n",
      "[[False  True  True False  True  True]\n",
      " [ True False  True False  True  True]\n",
      " [ True  True False False  True  True]\n",
      " [False False  True False  True False]\n",
      " [ True  True False False  True  True]]\n",
      "np.where(b<70,b,0)=\n",
      "[[ 0 44 65  0 43 59]\n",
      " [63  0 68  0 54 63]\n",
      " [48 65  0  0 66 48]\n",
      " [ 0  0 44  0 47  0]\n",
      " [40 51  0  0 46 59]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\r\n",
    "np.random.seed(7) #相同的种子可确保随机数按序生成时是相同的，结果可重现\r\n",
    "b = np.random.randint(40, 100, size=(5,6)) \t# \r\n",
    "print('b={}\\nb中小于70的元素为\\n\\n{}'.format(b,b<70))  \r\n",
    "ind = np.where(b<70,b,0)  # 返回的是一个tuple 类型\r\n",
    "print(\"np.where(b<70,b,0)=\\n{}\".format(ind))\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 3  数组的运算和排序\n",
    "\n",
    "\t\t相同形状的运算如下图。\n",
    "        \n",
    "![](images/n3.png)\n",
    "\n",
    "若是不同的形状的数组该怎么处理？可以求助于np.tile\n",
    "\n",
    "**数组的广播**\n",
    "\n",
    "通过特定操作，改变数组的形状\n",
    "\n",
    "    （1）参加运算的数组都向维数最大的数组看齐，维数较小的数组在前面加1补齐。\n",
    "    （2）结果数组的形状(shape)取各运算数组的各个轴上的最大值。\n",
    "    （3）若运算数组的某个轴的长度为1，则该轴可扩充为结果数组的对应轴的长度。若轴的长度不为1，则不能扩充。\n",
    "    （4）检查扩充后所有运算数组对应的轴的长度。若长度都相同则符合规则可以计算，否则违反规则无法计算。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "np.tile(a1,4)=\n",
      "[0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4]\n",
      "a3.reshape(5,4)=\n",
      "[[0 1 2 3]\n",
      " [4 0 1 2]\n",
      " [3 4 0 1]\n",
      " [2 3 4 0]\n",
      " [1 2 3 4]]\n",
      "a2.reshape(5,4)=\n",
      "[[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]\n",
      " [16 17 18 19]]\n",
      "the sum =\n",
      " [[ 0  2  4  6]\n",
      " [ 8  5  7  9]\n",
      " [11 13 10 12]\n",
      " [14 16 18 15]\n",
      " [17 19 21 23]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 数组的广播\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "a1 = np.arange(5)\r\n",
    "a2 = np.arange(20)\r\n",
    "a3 = np.tile(a1,4)\r\n",
    "print(\"np.tile(a1,4)=\\n{}\".format(a3))\r\n",
    "print(\"a3.reshape(5,4)=\\n{}\\na2.reshape(5,4)=\\n{}\".format(a3.reshape(5,4),a2.reshape(5,4)))\r\n",
    "print(\"the sum =\\n {}\\n\".format(a3.reshape(5,4) + a2.reshape(5,4)))\r\n",
    "\r\n",
    "# 思考，二维甚至更多维的数组怎么处理？\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**数组的排序**\n",
    "\n",
    "正排序和逆排序，以及获得对应的原数组下标\n",
    "\n",
    "*     np.sort(b)排序方法，按从小到大, 排序后返回新的有序数组。\n",
    "*     从大到小排序，用np.sort(-b)\n",
    "*     np.argsort(b) 将返回一个代表原数据顺序的有序下标数组\n",
    "*     多维数组排序时可指定 axis=0(行) / 1(列)，排序时默认按最大轴排序。    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a = \n",
      "[10 16 65 29 90 94 30  9 74  1]\n",
      "----------\n",
      "[ 1  9 10 16 29 30 65 74 90 94]\n",
      "[9 7 0 1 3 6 2 8 4 5]\n",
      "b = \n",
      "[[10 16 65 29 90]\n",
      " [94 30  9 74  1]]\n",
      "----------\n",
      "[[10 16  9 29  1]\n",
      " [94 30 65 74 90]]\n"
     ]
    }
   ],
   "source": [
    "#csb_print_codes\r\n",
    "np.random.seed(10)\r\n",
    "\r\n",
    "a = np.random.randint(1,100,10)\r\n",
    "print(\"a = \\n{}\".format(a))\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(np.sort(a))\r\n",
    "print(np.argsort(a))\r\n",
    "\r\n",
    "b = a.reshape(2,5)\r\n",
    "print(\"b = \\n{}\".format(b))\r\n",
    "print(\"-\"*10)\r\n",
    "\r\n",
    "print(np.sort(b,axis = 0)) #0轴排序，竖着排序\r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=[16  5  4  8 15  9 15 11  9  8]\n",
      "np.sort(b)=[ 4  5  8  8  9  9 11 15 15 16]\n",
      "np.sort(-b)=[16 15 15 11  9  9  8  8  5  4]\n",
      "\n",
      "np.sort(b)=[ 4  5  8  8  9  9 11 15 15 16]\n",
      "np.argsort(b)=[2 1 3 9 5 8 7 4 6 0]\n"
     ]
    }
   ],
   "source": [
    "np.random.seed(7)\r\n",
    "b=np.random.randint(1, 20, size=10)\r\n",
    "print(\"b={}\\nnp.sort(b)={}\\nnp.sort(-b)={}\\n\".format(b,np.sort(b), -np.sort(-b)))   # 注意最后一个写法\r\n",
    "print(\"np.sort(b)={}\\nnp.argsort(b)={}\".format(np.sort(b),np.argsort(b)))  # location of array\r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=\n",
      " [[16  5  4  8 15]\n",
      " [ 9 15 11  9  8]\n",
      " [ 7  5 17  8 13]\n",
      " [ 1 12  7 13  6]]\n",
      "\n",
      "np.sort(b)=\n",
      " [[ 4  5  8 15 16]\n",
      " [ 8  9  9 11 15]\n",
      " [ 5  7  8 13 17]\n",
      " [ 1  6  7 12 13]]\n",
      "\n",
      "np.sort(b,axis=1)=\n",
      " [[ 4  5  8 15 16]\n",
      " [ 8  9  9 11 15]\n",
      " [ 5  7  8 13 17]\n",
      " [ 1  6  7 12 13]]\n",
      "\n",
      "np.sort(b,axis=0)=\n",
      " [[ 1  5  4  8  6]\n",
      " [ 7  5  7  8  8]\n",
      " [ 9 12 11  9 13]\n",
      " [16 15 17 13 15]]\n"
     ]
    }
   ],
   "source": [
    "#多维\r\n",
    "np.random.seed(7)\r\n",
    "b=np.random.randint(1,20, size=(4,5))\r\n",
    "print(\"b=\\n\",b)\r\n",
    "print(\"\\nnp.sort(b)=\\n\",np.sort(b))   \r\n",
    "print(\"\\nnp.sort(b,axis=1)=\\n\",np.sort(b,axis=1))\r\n",
    "print(\"\\nnp.sort(b,axis=0)=\\n\",np.sort(b,axis=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "##  4 Numpy中的函数\n",
    "\n",
    "    一些函数可写为\"数组.函数名()\"的形式\n",
    "    b.max(),   b.min(),  b.ptp(),  b.sum(),  b.mean()\n",
    "    b.var(),  b.std() , np.median(b)  # 方差、标准差、中位数\n",
    "    b.argmax(),  b.argmin() \t# 最大值、最小值所对应的索引下标\n",
    "    np.prod(c)\t\t# 累乘  120\n",
    "\tnp.cumprod(c)  \t# 累乘并给出中间结果  array([1,2,6,24,120])\n",
    "\tnp.cumsum(c)     \t# 累加并给出中间结果\n",
    "    np.unique(b)   \t\t# 返回不重复的元素值 array([1, 3, 5, 7, 8])\n",
    "\tnp.all(b)           \t\t# b所有元素都为非0值则返回True\n",
    "\tnp.any(b)         \t\t# b有任意元素为非0值则返回True\n",
    "\t矩阵乘法运算的dot()函数\n",
    "\n",
    "\n",
    "\n",
    "\t* 缺失值:  nan表示缺失值，如数组中含有nan ,则函数运算结果为nan。\n",
    "    np.isnan(b)        \t# isnan()测试是否nan值\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a = \n",
      "[5 1 2 1 2 9 1 9 7 5]\n",
      "42\n",
      "4.2\n",
      "1\n",
      "1\n",
      "56700\n",
      "[    5     5    10    10    20   180   180  1620 11340 56700]\n"
     ]
    }
   ],
   "source": [
    "#自己创建array格式的数组，自行练习上述函数。\r\n",
    "#csb_print_codes\r\n",
    "np.random.seed(10)\r\n",
    "a = np.random.randint(1,10,10)\r\n",
    "print(\"a = \\n{}\".format(a))\r\n",
    "\r\n",
    "print(a.sum())\r\n",
    "print(a.mean())\r\n",
    "print(a.min())\r\n",
    "print(a.argmin())\r\n",
    "print(np.prod(a))\r\n",
    "print(np.cumprod(a))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**np.random 随机数模块**\n",
    "![](images/n4.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "np.random.rand(2,3):\n",
      "[[0.22 0.87 0.21]\n",
      " [0.92 0.49 0.61]]\n",
      "np.random.randint(1,100, 20)=[28 31 81  8 77 16 54 81 28 45 78 76 66 48 31 85 87 19 10 42]\n",
      "np.random.choice(b, 5)=[45 31 10 81 87]\n",
      "np.random.randint(10,100, [6,4]):\n",
      "[[79  6 59  1]\n",
      " [81  5 37 52]\n",
      " [28 32  3 69]\n",
      " [39 84 20 19]\n",
      " [ 8 31 63 12]\n",
      " [68 66 56  4]]\n",
      "np.random.shuffle(d):\n",
      "[[79  6 59  1]\n",
      " [28 32  3 69]\n",
      " [ 8 31 63 12]\n",
      " [81  5 37 52]\n",
      " [68 66 56  4]\n",
      " [39 84 20 19]]\n",
      "np.random.shuffle(d):\n",
      "[[28 32  3 69]\n",
      " [68 66 56  4]\n",
      " [81  5 37 52]\n",
      " [ 8 31 63 12]\n",
      " [79  6 59  1]\n",
      " [39 84 20 19]]\n",
      "np.random.shuffle(d):\n",
      "[[79  6 59  1]\n",
      " [ 8 31 63 12]\n",
      " [81  5 37 52]\n",
      " [39 84 20 19]\n",
      " [28 32  3 69]\n",
      " [68 66 56  4]]\n",
      "np.random.shuffle(d):\n",
      "[[28 32  3 69]\n",
      " [68 66 56  4]\n",
      " [81  5 37 52]\n",
      " [ 8 31 63 12]\n",
      " [79  6 59  1]\n",
      " [39 84 20 19]]\n",
      "np.random.shuffle(d):\n",
      "[[81  5 37 52]\n",
      " [68 66 56  4]\n",
      " [ 8 31 63 12]\n",
      " [79  6 59  1]\n",
      " [39 84 20 19]\n",
      " [28 32  3 69]]\n"
     ]
    }
   ],
   "source": [
    "#随机数练习\r\n",
    "import numpy as np\r\n",
    "np.random.seed(5)  \t\t# 设置随机数种子\r\n",
    "a=np.random.rand(2,3)  \t\t# 2x3小数数组\r\n",
    "b=np.random.randint(1,100, 20)  # 返回 [1,100)内20个随机整数\r\n",
    "c=np.random.choice(b, 5)\t# 从b中随机抽5个数据\r\n",
    "print(\"np.random.rand(2,3):\\n{}\\nnp.random.randint(1,100, 20)={}\\nnp.random.choice(b, 5)={}\".format(a,b,c))\r\n",
    "d = np.random.randint(1,100, (6,4))\r\n",
    "print(\"np.random.randint(10,100, [6,4]):\\n{}\".format(d))\r\n",
    "for i in range(5):\r\n",
    "    e = np.random.shuffle(d)  # 随机排列\r\n",
    "    print(\"np.random.shuffle(d):\\n{}\".format(d))  # 注意多维数组乱序的规律。行内随机排列\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**Numpy中的多项式运算**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "y=\n",
      "   6     5     4     3     2\n",
      "2 x - 3 x - 4 x + 5 x + 6 x + 7 x + 9\n",
      "y([0]=[9]\n",
      "np.roots(y)=[ 1.86+0.75j  1.86-0.75j -1.09+0.34j -1.09-0.34j -0.02+0.92j -0.02-0.92j]\n",
      "z={}    6     5     4     3      2\n",
      "2 x - 3 x - 4 x + 7 x + 10 x + 7 x + 12\n",
      "z.deriv()=\n",
      "    5      4      3      2\n",
      "12 x - 15 x - 16 x + 21 x + 20 x + 7\n",
      "        7       6       5        4         3       2\n",
      "0.2857 x - 0.5 x - 0.8 x + 1.75 x + 3.333 x + 3.5 x + 12 x\n"
     ]
    }
   ],
   "source": [
    "y = np.poly1d([2, -3, -4,5,6,7,9])  # 以列表形式给出多项式系数，y是构造的多项式\r\n",
    "print(\"y=\\n{}\".format(y)) # 2x^2 - 3 x -  4 \t\r\n",
    "print(\"y([0]={}\".format(y([0]))) # 分别计算x取值时多项式的值\r\n",
    "print(\"np.roots(y)={}\".format(np.roots(y)))  \t\t         \t# 计算方程 的根\r\n",
    "p = np.poly1d([2, 4, 0, 3]) \t# 构造多项式p=2x^3 + 4x^2 + 3 \r\n",
    "z = p+y                  \t\t# p, y 两个多项式相加\r\n",
    "print(\"z={}\",z)\r\n",
    "\r\n",
    "print(\"z.deriv()=\\n{}\".format(z.deriv())) # 微分\r\n",
    "\r\n",
    "print(z.integ())  \t     \t\t# 积分"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## numpy在图像处理中的应用\n",
    "\n",
    "图片简单变换(互补色，左右反转，上下反转，隔行画线等)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a.shape=(136, 178, 3), a.dtype=uint8, a[0,0,:]=[104  78  65]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\r\n",
    "from PIL import Image # 图像处理库\r\n",
    "\r\n",
    "path = 'images/'\r\n",
    "a = np.array(Image.open(path+\"cat.jpg\"))\r\n",
    "# 高x宽x颜色,数据类型,某个点的颜色值\r\n",
    "print(\"a.shape={}, a.dtype={}, a[0,0,:]={}\".format(a.shape, a.dtype, a[0,0,:]))\r\n",
    "\r\n",
    "b = 255 - a  # 互补色  RGB(255,0,0)\r\n",
    "im = Image.fromarray(b.astype('uint8'))\r\n",
    "im.save(path+'cat2.jpg')\r\n",
    "\r\n",
    "\r\n",
    "c = a.copy()\r\n",
    "c[::2] = 0         #黑色横条\r\n",
    "Image.fromarray(c).save(path+'cat3.jpg')\r\n",
    "\r\n",
    "c = a.copy()\r\n",
    "c[:, ::2] = 255 #白色竖条\r\n",
    "Image.fromarray(c).save(path+'cat4.jpg')\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 5 数组组合和存取 \n",
    "\n",
    "改变数组维度：np提供了如下方法可改变数组的维度\n",
    "\n",
    "\t 1. reshape()方法\n",
    "     2. 转置矩阵: 将行/列交换 d=c.transpose()   #  或  c.T\n",
    "   \n",
    "\t3.增加维度（拼接）\n",
    "    \tnp.hstack((m1,m2)): m2放在m1的下方。m1和m2有相同的列数\n",
    "    \tnp.vstack((m1,m2)): m2放在m1的最右方。m1和m2有相同的行数\n",
    "      \t函数的具体用法，自行百度。\t\t\n",
    "    \n",
    "    4. 将多维数组展平为一维数组\n",
    "    \tb.ravel() \t# 将多维的b转为一维的d1, 视图（没有创建新的空间存储扁平化后的矩阵）\n",
    "    \tb.flatten() \t#将多维的b转为一维的d2, 是复制，不是视图\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a= [47 22 82 19 85 15 89 74 26 11 86 81 16 35 60 30 28 94 21 38 39 24 60 78]\n",
      "b= [[47 22 82 19 85 15 89 74]\n",
      " [26 11 86 81 16 35 60 30]\n",
      " [28 94 21 38 39 24 60 78]]\n",
      "\n",
      "b.reshape(-1,6)=\n",
      " [[47 22 82 19 85 15]\n",
      " [89 74 26 11 86 81]\n",
      " [16 35 60 30 28 94]\n",
      " [21 38 39 24 60 78]]\n",
      "\n",
      "b.reshape(12,-1)=\n",
      " [[47 22]\n",
      " [82 19]\n",
      " [85 15]\n",
      " [89 74]\n",
      " [26 11]\n",
      " [86 81]\n",
      " [16 35]\n",
      " [60 30]\n",
      " [28 94]\n",
      " [21 38]\n",
      " [39 24]\n",
      " [60 78]]\n"
     ]
    }
   ],
   "source": [
    "# reshape 方法\r\n",
    "import numpy as np\r\n",
    "np.random.seed(1)\r\n",
    "\r\n",
    "a = np.random.randint(10,100,size=(24))\r\n",
    "print(\"a=\",a)\r\n",
    "\r\n",
    "b = a.reshape(3,8)\r\n",
    "print(\"b=\",b)\r\n",
    "print(\"\\nb.reshape(-1,6)=\\n\",b.reshape(-1,6))  # 自动计算有多少行\r\n",
    "print(\"\\nb.reshape(12,-1)=\\n\",b.reshape(12,-1))  # 自动计算有多少列\r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "m=\n",
      "[[5]\n",
      " [8]\n",
      " [5]\n",
      " [0]]\n",
      "arr=\n",
      "[[28 21 38 39 24 60]\n",
      " [78 23 19 17 73 71]\n",
      " [32 67 11 10 70 18]\n",
      " [23 57 82 40 81 13]]\n",
      "merge=\n",
      "[[28 21 38 39 24 60  5]\n",
      " [78 23 19 17 73 71  8]\n",
      " [32 67 11 10 70 18  5]\n",
      " [23 57 82 40 81 13  0]]\n",
      "mergev=\n",
      "[[28 21 38 39 24 60]\n",
      " [78 23 19 17 73 71]\n",
      " [32 67 11 10 70 18]\n",
      " [23 57 82 40 81 13]\n",
      " [ 0  1  7  6  2  4]]\n"
     ]
    }
   ],
   "source": [
    "# 增加一个维度\r\n",
    "import numpy as np\r\n",
    "np.random.seed(1)\r\n",
    "\r\n",
    "m = np.random.randint(0,9,[4,1]) # 生成4行六列的矩阵\r\n",
    "n = np.random.randint(0,9,[1,6])\r\n",
    "arr = np.random.randint(10,90,[4,6])\r\n",
    "mergeh = np.hstack((arr,m))  # 水平增加一列，放在最后\r\n",
    "mergev = np.vstack((arr,n)) # 垂直（下）增加一行\r\n",
    "print(\"m=\\n{}\\narr=\\n{}\\nmerge=\\n{}\\nmergev=\\n{}\".format(m,arr,mergeh,mergev))\r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------转置和降维---------\n",
      "m转置前\n",
      "[[5 8 5 0 0 1]\n",
      " [7 6 2 4 5 2]\n",
      " [4 2 4 7 7 1]\n",
      " [7 0 6 7 6 1]]\n",
      "m转置后\n",
      "[[5 7 4 7]\n",
      " [8 6 2 0]\n",
      " [5 2 4 6]\n",
      " [0 4 7 7]\n",
      " [0 5 7 6]\n",
      " [1 2 1 1]]\n",
      "\n",
      "m扁平化\n",
      "[5 8 5 0 0 1 7 6 2 4 5 2 4 2 4 7 7 1 7 0 6 7 6 1]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\r\n",
    "np.random.seed(1)\r\n",
    "\r\n",
    "m = np.random.randint(0,9,[4,6]) # 生成4行六列的矩阵\r\n",
    "print(\"-------------转置和降维---------\")\r\n",
    "print(\"m转置前\\n{}\\nm转置后\\n{}\\n\".format(m,m.T))\r\n",
    "print(\"m扁平化\\n{}\\n\".format(m.ravel()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**数组元素的插入和删除**\n",
    "\n",
    "\t插入元素\n",
    "    \tnp.append(arr, values, axis=None): 为原始array添加一些values，返回新数组，原数组保持不变。\n",
    "\t\t参数：\n",
    "\t\t\tarr:需要被添加values的数组\n",
    "\t\t\tvalues:添加到数组arr中的值（array_like，类数组）\n",
    "\t\t\taxis:可选参数，如果axis没有给出，那么arr，values都将先展平成一维数组。\n",
    "    \t\t注：如果axis被指定了，那么arr和values需要有相同的shape，否则报错\n",
    "   \n",
    "   \n",
    "\t删除元素\n",
    "   \t\tnumpy.delete(arr, obj, axis):所有操作都是在arr的副本进行，需要有变量接收返回值。\n",
    "\t\t\tarr：需要处理的矩阵\n",
    "\t\t\tobj：在什么位置处理\n",
    "\t\t\taxis：可选参.axis=None：arr会先按行展开，然后按照obj，删除第obj-1（从0开始）位置的数，返回一个行矩阵。\n",
    "\t\t\taxis = 0：arr按行删除. axis = 1：arr按列删除.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "b=\n",
      "[1 3 5 7]\n",
      "c=\n",
      "[ 1  3  5  7  9 10]\n",
      "\n",
      "--------np.delete------------\n",
      "m=\n",
      " [[1 5 2 1 1 6 4 8 3]\n",
      " [2 1 7 8 4 7 6 2 2]\n",
      " [4 5 1 2 4 5 3 5 1]\n",
      " [6 4 2 3 8 1 5 2 8]\n",
      " [3 8 8 3 2 1 7 2 4]\n",
      " [8 7 8 6 5 4 6 2 4]\n",
      " [7 1 1 3 3 8 8 8 8]\n",
      " [2 8 4 5 3 7 7 1 1]]\n",
      "np.delete(m,1,axis=1) [[1 2 1 1 6 4 8 3]\n",
      " [2 7 8 4 7 6 2 2]\n",
      " [4 1 2 4 5 3 5 1]\n",
      " [6 2 3 8 1 5 2 8]\n",
      " [3 8 3 2 1 7 2 4]\n",
      " [8 8 6 5 4 6 2 4]\n",
      " [7 1 3 3 8 8 8 8]\n",
      " [2 4 5 3 7 7 1 1]]\n"
     ]
    }
   ],
   "source": [
    "# 插入元素\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "b=np.array([1, 3, 5, 7])\r\n",
    "c=np.append(b, [9, 10])   # 插入， b不变，返回新数组c\r\n",
    "print(\"b=\\n{}\\nc=\\n{}\\n\".format(b,c))\r\n",
    "# 二维矩阵中的插入操作，可以用hstack,vstack实现\r\n",
    "\r\n",
    "print(\"--------np.delete------------\")\r\n",
    "m = np.random.randint(1,9,[8,9])\r\n",
    "print(\"m=\\n\",m)\r\n",
    "m1 = np.delete(m,1,axis=1) # 删除第二列所有元素。\r\n",
    "print(\"np.delete(m,1,axis=1)\", np.delete(m,1,axis=1))\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**数组分割**\n",
    "\n",
    "np.hsplit() 能将数组沿着它的水平轴分割，或者指定返回相同形状数组的个数，或者指定在哪些列后发生分割.\n",
    "np.vsplit() :功能同上，针对行的操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------b and x1 and x2------------\n",
      "[[47 22 82 19 85 15]\n",
      " [89 74 26 11 86 81]\n",
      " [16 35 60 30 28 94]\n",
      " [21 38 39 24 60 78]]\n",
      "[[47 22 82]\n",
      " [89 74 26]\n",
      " [16 35 60]\n",
      " [21 38 39]]\n",
      "[[19 85 15]\n",
      " [11 86 81]\n",
      " [30 28 94]\n",
      " [24 60 78]]\n",
      "\n",
      "b=\n",
      "[[47 22 82 19 85 15]\n",
      " [89 74 26 11 86 81]\n",
      " [16 35 60 30 28 94]\n",
      " [21 38 39 24 60 78]]\n",
      "y1=\n",
      "[[47]\n",
      " [89]\n",
      " [16]\n",
      " [21]]\n",
      "y2=\n",
      "[[22 82 19]\n",
      " [74 26 11]\n",
      " [35 60 30]\n",
      " [38 39 24]]\n",
      "y3=\n",
      "[[85 15]\n",
      " [86 81]\n",
      " [28 94]\n",
      " [60 78]]\n",
      "\n",
      "----演示np.vsplit()的类似操作-------\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\r\n",
    "np.random.seed(1)\r\n",
    "\r\n",
    "# 水平方向将b分成两个同形状的矩阵。\r\n",
    "b = np.random.randint(10,99,(4,6)) \r\n",
    "x1,x2 = np.hsplit(b,2)\r\n",
    "print(\"-------b and x1 and x2------------\\n{}\\n{}\\n{}\\n\".format(b,x1,x2))\r\n",
    "\r\n",
    "y1,y2,y3 = np.hsplit(b,[1,4])\r\n",
    "print(\"b=\\n{}\\ny1=\\n{}\\ny2=\\n{}\\ny3=\\n{}\\n\".format(b,y1,y2,y3))\r\n",
    "\r\n",
    "print(\"----演示np.vsplit()的类似操作-------\")\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**Numpy文件的读写**\n",
    "\n",
    "\tnumpy.save(path,varList):\n",
    "       path: 文件保存路径\n",
    "       varList: 将要保存的变量列表，中间用逗号隔开。\n",
    "       \n",
    "\tnumpy.load(path,varList):\n",
    "      变量意义同上。\n",
    "      \n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 95.  96.  97.]\n",
      " [ 98.  99. 100.]]\n"
     ]
    }
   ],
   "source": [
    "# text文件的操作。\r\n",
    "b=np.arange(95, 101).reshape(2,3)\r\n",
    "np.savetxt('data/data.txt', b)  # 将数组b保存到data.txt 文件中,保存时默认按科学计数法格式\r\n",
    "\r\n",
    "c = np.loadtxt('data/data.txt') # 加载文件\r\n",
    "print(c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(10000, 10000) int64\n",
      "[42 11 34 ... 47 61 68]\n"
     ]
    }
   ],
   "source": [
    "# array和csv文件的操作\r\n",
    "import numpy as np\r\n",
    "y = np.random.randint(0,100,(10000,10000))\r\n",
    "np.save(\"data/yFile\",y)\r\n",
    "\r\n",
    "yy = np.load(\"data/yFile.npy\")\r\n",
    "print(yy.shape,yy.dtype)\r\n",
    "print(yy[1,:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**数组练习1**\n",
    "![](images/n5.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**数组练习2**\n",
    "\n",
    "用NumPy给出一个骰子研究实例。\n",
    "\n",
    "    有三颗骰子，每次一起抛出，现在用随机函数模拟抛掷1000次(1000x3)。\n",
    "    这1000次中有多少次投出“666”？\n",
    "    有多少次出现三个骰子点数一样的情况？\n",
    "    如果假定最初有500个筹码，三个骰子的点数之和大于10算赢，小于等于10算输，每次输赢一个筹码，那么最后是赢还是输？\n",
    "    曾经达到的最大输赢数是多少？\n",
    "\n",
    "输赢示例如下：\n",
    "![](images/n6.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "864 266 660\n",
      "count666=477,countEqual=2776,\n",
      "win=\n",
      "[500 501 500 ... 660 661 660]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f98dc609090>]"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 方法1：通常的方法\r\n",
    "import numpy as np\r\n",
    "np.random.seed(10)\r\n",
    "\r\n",
    "num = 100000\r\n",
    "count666 = 0 \r\n",
    "countEqual = 0\r\n",
    "winLose = np.array([500])\r\n",
    "for i in range(1,num+1): # 抛掷1000次\r\n",
    "    x = np.random.randint(1,7,(3,1)) # 抛出的结果\r\n",
    "    # print(winLose)\r\n",
    "    if np.sum(x) == 18:\r\n",
    "        count666 +=1\r\n",
    "    if x[0]==x[1] and x[1]==x[2]:\r\n",
    "       countEqual+=1\r\n",
    "    if np.sum(x) <= 10: # 输掉\r\n",
    "        winLose=np.append(winLose,winLose[i-1]-1)\r\n",
    "    else: # 赢\r\n",
    "        winLose=np.append(winLose,winLose[i-1]+1)\r\n",
    "print(max(winLose),min(winLose),winLose[-1])\r\n",
    "print(\"count666={},countEqual={},\\nwin=\\n{}\".format(count666,countEqual,winLose))\r\n",
    "\r\n",
    "# 绘图\r\n",
    "np.set_printoptions(threshold=10000)  # 输出数据个数的门槛\r\n",
    "# 筹码变化图\r\n",
    "import matplotlib.pyplot as plt\t\t# 引入绘图库\r\n",
    "#plt.rcParams['font.sans-serif'] = ['SimHei']\t# 指定中文黑体字体，此行在本地执行的时候可以打开注释，以便显示中文。\r\n",
    "plt.xticks(fontsize=14)\t\t# 设x轴文字大小\r\n",
    "plt.yticks(fontsize=14)\t\t# 设y轴文字大小\r\n",
    "plt.title('筹码变化', fontsize=16) \t\t# 设标题文字\r\n",
    "plt.plot(winLose) \t\t\t# 画筹码变化折线图   \r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**部分Numpy函数**\n",
    "\n",
    "补充下面的函数：功能，相关参数\n",
    "\n",
    "     np.logical_and（）\n",
    "     np.column_stack（）\n",
    "     np.where（）\n",
    "     np.cumsum（）v.s. np.cumprod()\n",
    "     np.argmax（）v.s. np.argmin()\n",
    "     \n",
    "\n",
    "当抛币次数为1e5,1e7,1e9时，感受俩个程序的计算时间差距。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "投掷出666的次数: 5\n",
      "666出现在第x次 (array([ 48, 699, 837, 871, 919]),)\n",
      "\n",
      "三次投掷点数均相同的次数: 29\n",
      "\n",
      "初始筹码: 500 , 盈亏点: 10 , 每次输赢1\n",
      "最后筹码: 458\n",
      "曾经的最高筹码数501, 出现在第0次\n",
      "曾经的最低筹码数447, 出现在第534次\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7f98dc58f9d0>]"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAEOCAYAAACEiBAqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJztnXl8XFXZ+L9PlskySdpm0qYbpSu1C1Ch0EUqmyAKKILIIiCCoILw4o/3xaIIvKIVERGV5RVFWQQBUYtSZG/ZytaWUmgp3Te6ZG+TSZPJcn5/3HsndyYzySSdyWzP9/PJJ3PPPffOOTN3znPOc55FjDEoiqIo2UtOshugKIqiJBcVBIqiKFmOCgJFUZQsRwWBoihKlqOCQFEUJctRQaAoipLlqCBQFEXJclQQKIqiZDl5yW6AoiQDETkOeAaoi3A6F/gY2AKcCrRGqFMGXG2MeUBEOoBdUd5quDEmT0QuBn4L7ItQpwBYZIy5uA9dUJS4oYJAyWb+ZYw5N7xQRCYCf7QPLzTGPBuhzk9dh9XGmNGR3kBEdrgO7zHGzI9Q53PABX1quaLEEVUNKYqiZDkqCBRFUbIcFQSKoihZjgoCRVGULEcFgaIoSpajgkBRFCXLUUGgKIqS5aggUBRFyXJUECiKomQ5KggURVGyHBUEiqIoWY7GGlKylVbgmLBYQG7exApI90cRiXaP79v/23u4j4MfuEBEosUUeqKX6xUlYYgxJtltUBRFUZJIWqwIKioqzNixY5PdDEVRlLRi+fLlNcaYob3VSwtBMHbsWJYtW5bsZiiKoqQVIrI1lnq6WawoipLlqCBQFEXJclQQKIqiZDkqCBRFUbIcFQSKoihZjgoCRVGULEcFgaIoSpaT0YLgn+/t4Gu/f5O9+9uS3RRFUZSUJSZBICI3i4gJ+9vtOi92nZ0isl9ElojItLB7DBGRh0Vkr/33sIgMjneH3Pz2pQ28s7mO+X9flci3URRFSWv6siL4GBjh+jvUde464FrgKuAooAp4QURKXXUeBY4ATrH/jgAe7nfLY6Agz+retrrmRL6NoihKWtOXEBPtxpjd4YVihWa8BrjVGPN3u+wbWMLgfOD3IjIFa/A/xhjzpl3n28BrIjLZGPPxAfYjIk7USI2rpyiKEp2+rAjG26qfzSLymIiMt8vHAcOB552Kxpj9wKvAXLtoDtAELHXd7w2s0LxzSRCDiiw51xxoT9RbKIqipD2xCoK3gYuxZvWXYQ38S0XEZ78G2BN2zR7XueFAtXHFvLZfV7nqhCAil4vIMhFZVl1dHWMzQ/n1OTMAGFpa0K/rFUVRsoGYVEPGmP+4j0XkLWAT8A3grQS0C2PMfcB9ADNnzuyXcmfEoCJOPXQEa3fvi2vbFEVRMol+mY8aY5qA1cAkwNk3qAyrVuk6txsYKq5UT/brYa46CaHc66HWHwge+1vbaWnrSORbKoqipBX9EgQiUgh8CtgFbMYazE8KOz+Prj2BN4ESrL0ChzmAl9B9g7hT7vXQ0NxGe0cnANNueo7Tf/d6It9SURQlrYjVj+B2ETlWRMaJyCzgSaxB/EFb138n8AMROVNEpgMPYG0OPwpgjPkIeBbLgmiOiMwBfg88nSiLIYeKEg8A9c1dTmXrq5oS+ZaKoihpRazmo6OBvwIVQDXWvsBsY4yT/eY2oAi4GxiCtbl8sjGm0XWP84HfAc/Zx/8CvndArY+Bcq+1UVzrb6Xc60n02ymKoqQdsW4Wn9vLeQPcbP9Fq1MPXNCHtsUFZ/CvawrQUNK1V7C5xs+4Cu9AN0dRFCXlyOhYQ9ClGqr1B6hzbRoff/uSJLVIURQltch4QeCsCGqbWkOshxRFURSLjBcEg4s9iECdP0BtkwoCRVGUcDJeEOTmCOXFHqoaW7ny0RUh515eG+4MrSiKkn1kvCAASz20Zld37+JLH1yWhNYoiqKkFlkjCDZV+4PHZx0xGtCopIqiKJAlgqCipICm1sgRSGubWge4NYqiKKlFVgiCcEeyGWO6EqMd+dMXCbR3DnSTFEVRUoasEwQv/r9juWDWGHKk63xDs1oTKYqSvWSFIPDZTmV5OcKEoV5EhGkjBwXP16hZqaIoWUx2CAI73lC514MrEnaQOnU0UxQli8kKQeCohtwqomtPPiT4utavG8aKomQvWSEIHNWQ8x/guMnDeO/HVgoF9ThWFCWbyQ5BYK8EHBWRw6CifHJzRFVDiqJkNVkhCAYXe8iR7makOTnCkGKPBqNTFCWriTUxTVqTmyP87CuHcuTBQ7qd83k96lSmKEpWkxWCAOC8o8dELPeVeFQ1pChKVpMVqqGeKPeqIFAUJbvJekHg83qoUdWQoihZjAqCkgL2tbTT1qHxhhRFyU6yXhA4lkT1qh5SFCVLyXpB4PgYqAmpoijZStYLgq7k9ioIFEXJTrJeEPhKLG9jjTekKEq2ooLAXhGoCamiKNlK1guCQUX5APzvv9ewt7ktya1RFEUZePosCETkehExInKXq6xSRB4QkZ0i0iwiz4rIpLDrCkTkdyJSIyJ+EfmXiIyORycOhBxXqrJ3ttQlsSWKoijJoU+CQERmA5cDq1xlAiwEJgFnAJ8GtgIviojXdfmdwFnAecA8oAx4WkRyD6QD8cQYk+wmKIqiDDgxCwIRGQQ8AlwC1LtOTQJmA1cYY94xxnwMfBcowhr0nWsvBf7HGPOCMWYFcCFwGPC5eHQkHuzdr6ohRVGyj76sCO4DnjTGLA4rd4L8tzgFxphOoBU4xi46EsgHnnfV2Q58BMztY5vjTmmhFXtPN4wVRclGYhIEInIZMBG4IcLptcA2YIGIlIuIR0R+AIwGRth1hgMdQE3YtXvsc5He83IRWSYiy6qrq2NpZr9ZOv8EQAWBoijZSa+CQEQmAwuA840x3XQndtmZwASgFmgGjgf+A/Q7gI8x5j5jzExjzMyhQ4f29zYxUVqYz8hBhdSoU5miKFlILCuCOUAFsFpE2kWkHTgWuMI+LjDGLDfGzAAGAyOMMacAPmCTfY/dQK59HzeV9rmk4yspoE6dyhRFyUJiEQQLgUOBGa6/ZcBj9uvgNNoYs9cYU22bjs4EnrJPLQfagJOcurbp6BRg6YF348DRvASKomQrvWYoM8Y0AA3uMhHxA3XGmA/t47Ox9P9bsYTGb4CFxpjn7XvsFZH7gdtEpApLhXQHlhnqi/HrTv/xeT1sqGpKdjMURVEGnHilqhyBNbBXAruAh4BbwupcA7QDj2OZlr4EXGSM6YhTGw4IXREoipKt9CvEhDHmOGPM91zHvzXGHGSM8RhjDjbG/NgYEwi7ptUYc5UxxmeMKTbGnG6bkKYEvpIC9rd10Bxo79f197++mbHzF/H35Tvi3DJFUZTEkvWxhhx8BxiO+pan1wBw7d/ej1ubFEVRBgIVBDblGoVUUZQsRQWBja9EBYGiKNmJCgIbn9eKlFHTpL4EiqJkFyoIbMoPYEXQ2WnItcNZV9j3URRFSRdUENh4PbkU5OX0SxDsa2mjo9NQ7MmlvrmNzk4NZ60oSvqggsBGRPB5PdT2IAiaA+3cvXgD7R2dtLZ3cNlDy3hq5SfBayZVltLRaXjmw10D1WxFUZQDRgWBi/ISD7U97BEs+biaXz73Me9tb+ChpVt5Yc0e/uuxlUGT00nDSgD43qPvDUh7FUVR4oEKAhc+b0GPqiFHSNQ2tdLY0hWI1QlWd0hlSWIbqCiKkgBUELjoTTXknAuvs6W2GYBJw0oT1zhFUZQEEa9YQxlBuddDbVOA6//xAX99ZxsA3z1uAmt27qMgL4fn1+wBYP2eJh5YuiV43a3/WQvAwb7iYFmgvRNPnspZRVFSHxUELspLPOxv6wgKAYB7l2zsVu/pVZE3g8cPLWHayDJW79xHfXOAyrLChLVVURQlXuiU1UWFt6D3SkB9c3T10feOnwj0P2aRoijKQKOCwIUTb6g3OnrwE3Du8fya3fhb+xfJVFEUZSBRQeCivI9ewRUlXSsIx7PYiVl054vrueD+t+PXOEVRlAShgsCFWzX0xUOH91r/teuOD75eeaOVhdPnusd72xq6XaMoipJqqCBw4V4RfH5az4LgoPIiijy5wePSwnwABhXlJ6ZxiqIoCUIFgQuvJzdo8jm0tOeN4/IoG8s5topIURQlXVBB4EJEqLA3eytKCjh6XHnI+YN9xfzXiZOs83a9q06YyCWfGRdS75iJFQPQWkVRlPigfgRhlJd42Lm3hXKvhye+Pafb+VfXVVv1bEFw7cmTu9X5y7dmcduza7nv1U10dhpdJSiKktLoiiCMcm8BIjCkOLIFkSMAerMwKvd6aO807HPFJFIURUlFVBCEUVHiobzYEzQHDcfZOxha0vMegmNaOuMnL7Dk46r4NlJRFCWOqGoojO8cO4HTDhsR9XxlWSG3ffUwTvzUsB7v43ZO+/WL6zlucs/1FUVRkoUKgjAOqSzlkMqeo4h+beZBvd4nxEvZaMYyRVFSF1UNJQifaw+hrUMFgaIoqUufBYGIXC8iRkTucpWViMjvRGSHiOwXkY9F5Pth1xXYdWpExC8i/xKR0fHoRCriXhHk5arVkKIoqUufBIGIzAYuB1aFnboDOBW4EJgC/Ay4VUQudNW5EzgLOA+YB5QBT4tILhlIQV4uy274HJMrSxFRQaAoSuoSsyAQkUHAI8AlQH3Y6bnAw8aYxcaYLcaYh4C3gFmuay8F/scY84IxZgWW0DgM+NyBdyM1qSgpYMqI0h7zICuKoiSbvqwI7gOeNMYsjnDudeB0ETkIQETmAjOAZ+3zRwL5wPPOBcaY7cBHWEIkY/GVFLCjfj8X3v82re0dACzfWs+9SzZy9+INGN1IVhQlycRkNSQilwETgQuiVLka+D2wTUScIPxXGWOetl8PBzqAmrDr9tjnIr3n5VhqKMaMGRNLM1MSZ6/gtfU1/G3ZDi6YfTBn3bs0eP6ETw1jyoiyZDVPURSl9xWBiEwGFgDnG2OiuclehTWz/xLW7P/7wO0ickp/G2aMuc8YM9MYM3Po0KH9vU3ScUcjbWnr6HZ+f4QyRVGUgSSWFcEcoAJY7dr0zAU+KyLfAXzAz4GzjTH/ts+vEpEZwH9jqYd229dUANWue1cCrx1oJ1KZHNdGcSQtUL1fU1oqipJcYhEEC4FlYWV/BtZjrRTA0v+HT2076FpxLAfagJOARwFs09EpwFIyGPeK4GfPfMSSdaHhJmpVECiKkmR6FQTGmAYgJNWWiPiBOmPMh/bxK1jmok3AVuBY4CLgOvsee0XkfuA2EakCarFMTlcBL8avO6nHF6YP51vHjOOPr28G4I0NtSHnNcm9oijJJl6execC72KZl64B5gM/Bu5y1bkG+CfwOPAG0AScbozJaCV5To5ww2lTo56v86tpqaIoyaVfsYaMMceFHe8GvtnLNa1Ym8pX9ec9MxVVDSmKkmw01tAAURElbLWqhhRFSTYqCAaI139wPNeedEjw+LHLZzNvUgV1uiJQFCXJqCAYIArzc5k+elDweHyFl6GlBSoIFEVJOioIBhCfKyLpEK8Hn9dDrb81a8JMPPvhLl5euyfZzVAUJQwVBAOIE26irDCP/Nwcyr0FtLR10hzIaMOpIN/5ywoueSDcJUVRlGSjgmAA8XmtDWNn49hJXqPqIUVRkokKggGkyJNLsSc3uDJwVEXZYEKaLeovRUlHVBAMMENLC4IrAkcgnHH3G8lsUsJ5b1s9465/Jnjc1tGZxNYo6cjY+YsYO38Rm6qbkt2UjEST1w8wvzjrsKAAcPsWdHYacnIyM5PZY+9sDzmubw4wrLQwSa1R0pmX11YxfmhJspuRceiKYICZPd7HIZWlQGhe430t0SJ8Zx66J6L0hf1ZYkyRTFQQJJFiT1e65hqXh3GgvZPnV++mal9LMpqVcOrUmzpjaWpt57nVu3llXXXvlWOk1hWPq71T95oSgaqGkog7qb17lrzgmY94YOkWALbceupANyvuhP94a3RFkLFc+sC7vL25DoCHLz2aeZMOPKmU+7dR36zPTiLQFUGS+ft35wChUUjf3VKXrOYkhIbmAAf7innp2mMBqGvSiKuZiiMEALbWNsflnm6rOl1NJgYVBElm1OBiIFQ1lGmWljX+AGPKixnr8yKiewTZQrweYycwY2lhXlaYWicDVQ0lmSFeK4PZX9/ZRtW+FsqK8kPyGBtjQlRI6caanft4f3sDX54xktwcYUixR1VDGYgxhuueXBVS9te3t/HAG5v523fmhhhGxEq9P8A9SzZwv53UadKwEhUECUIFQZIpyMultDCP1Tv3sXrnPgA+PWZw8HxTazulhfnRLk95zrjH8pEos/vg83p0eZ+B1DQF+NvyHSFla3ZZz/PPFn3Er752eJ/v+Ytn1/LYu12mx2PKi1m+rf7AGqpERFVDKYAvbLa0zaVbTXc1SqDdch5zFjXlXk/a90npToNrE3fWuHJGDuryE2kOtPfrnk2todf5Sgp0EpEgVBCkAOHL5lp/gBH2D6kmwx58X4knxBxQyQzCn9Mh/VAF9Ua514M/0EFLm/oVxBsVBCmAt6C7hm6S7XSWKbPnyjJLsPm8BarnTQOeWLadM+/pOfTJmxtrOfaXi/G3tnPeH94Klh9UXkx+btfQYgy8sq6aE361JDiI797bEgwbEc5j72xj7PxFwdWkQ0VJ9sTmGmhUEKQohwyz3OjTPbn9sNICcnOEyz87HrBmdQ3NbbRrvKGU5r1t9azY1tDj7Pv9HQ1srW1mU7U/WHbGjJH875emhdQLdHSyclsDm6r97N5rOUmucOn6wwf8Xzy7FoAd9fuDZU9d+RnK7ei9qh6KPyoIUpSDyrublaYbxhjqmwN8a9644AzRCb1d35w9ITXSEee562n2XWv7g6yvagyW/fLsw7utcGubWoPqQOe/20Q6mpPYhqquAHOHHzQ4qEKtSfPJUSqigiBFKcq3Qlans2qosbWdtg5DhbcruJ6TkyEe+wTtHZ28p1YkCcF57havreKJZduDM3mHDVWNwZXAeteA7VYJAYweUsT7O/ayucaq6/gEuGNrLd8a+TsMdERWDb22rqbP/VF6RgVBCnDqoSO6lU0fNSjtLWycJbx7M9x5HY/l/R0vrOMr9yzlI9tMUYkfznN3w8IPue7JVcz++Ush5z93x6u8tLYKgPV7Grtdf8aMkYA1kwd4bX1NyH3dz/UVj6zosS2F+dYw5Tw7f3pjM3t1RRlX1I8gBTjnqIM47XDrh5OXI7S2dzKoKB+f10NNGodjcGb95SVdgsAXxw2/93c0AFDd2MqU7rJUOQB6eu7C80k4K4Jnrp4XLPvG3LGcPfMg7l2yMaSu873H+lxfesw4rjtlMgAlLpVTXXOAQcXp61+TauiKIAUQEUoK8igpyKMwP5dBRbbzVUlBWq8IHDVAqGoo/uk596s5YVwJtHfS2BLd9r8+7LvbVmf5vYwaXBQsE5GI1nDOM1HnD1Ba2HXe7WvQ1tG1gZCfm0NBXm7wng7pbkSRaqggSGHSXjVkt929Ihhc7EEEqhpbuOCPb3PDwg96vU9bRydf+783+enTa0LKBWtguGHhh5oKM4785a2tEcudzzh8NWeMtZItK+o+8IdHR3HUOnX+AOMrvMHy655cxZQfP8vY+Yu6OZJFYsEza3uto8ROnwWBiFwvIkZE7nKVmSh/d7vqiIjcLCI7RWS/iCwRkWmR30UBa/Zc2xRI20HOGTDcntNOvKGXPqri9Q01/OWtbb06CL22vpp3ttTxx9c30+EKaW3ssGbVja1UNeoMMV78JEzgOjgDdKTJyRCvJ2JMrIvnju1W9puX1lPbFKCipIAxtnXc06t2hazsPHnW0PSteeNCrv22bYa8fGt9yLOgHBh9EgQiMhu4HFgVdmpE2N/pdvkTrjrXAdcCVwFHAVXACyJS2vdmZwflXg+Bjs6YZkipSG1TgGJPLoX5uSHl5V4Pu1xWKL2tetyqAncoA7f6okE3DxPGDadOAbq+p0j6/fAwKcHykgJOmloZUtbS3kGtv5Vyr4c7z50R8brfnjuDLbeeGpLOFeD7Jx0SfK25CeJHzIJARAYBjwCXACH2XsaY3e4/4MvAOmPMK/a1AlwD3GqM+bsx5kPgG0ApcH58upJ5+OwfQbqqh+r8rcHNYTc+r4e9+9tc9Xrun1t/7K5b67I80rAViWOcrcKpjWDx4wnzD4mVOn8AX0lByP6Rm5KCyBvB7klFuv4uUpG+rAjuA540xizuqZKIlADnAn9wFY8DhgPPOwXGmP3Aq8DcPrQhq3BmWenqUl/rDwS9Qd2EDxq99c894J/061e56akPGTt/EZ80dHme6qAQHyIFiHNm5e6NXoeJtgd8pO/ZwVH/ODz69jbaOgw+rydk/8iNeyM5Gif/+tWo5xY88xHff3wllzzwLncv3tDrvbKdmMxHReQyYCJwQQzVzwc8wIOusuH2/z1hdfcAo6K85+VYaijGjBkTSzMzDsduujZNvYtrmwIMd0WhdOgWZK8XU8JwQfHgm12bmXMn+Fi6sTZtP6NUw/kcT55ayY9OncKefa1dvh/2qqumKUBZYR63nDGdvfvbuPGp1VFVQwD/8/nJzDhoMLPGl3P0z7r8Ecq9HkoK8vjS4SP51/s7Abhw9sGMH+oN+h9E4tYzD2X+P3o2Mli+tZ6qxhYa/G3kpG86jwGj1xWBiEwGFgDnG2NiUcReBjxljDmg7NXGmPuMMTONMTOHDj3wvKfpiDNzTldTuTp/IOIA4XgXO3uLvc3m65oC3axPHI49ZCgi6btqSjWcz/Gcow7iYJ+Xo8eVd/P9qPO3MnxQIV+eMSooJHoSBIX5uZx++EiGlRZy9NjyYLmzGvjqkaMBmHHQYG45Yzrf/My4iPdx+Jxrz6G1PbKhQZ0/wJ59rTS2tuuzEQOxqIbmABXAahFpF5F24FjgCvs4uCYUkRnATELVQgC77f+VYeWVrnNKGF3hGNLvQTbGUOcPRFz6OwPLWJ+X3BzhmQ92Ba95c2NtNyupWn+AiUNLIr6PY4X0nw92pa11Varw7Ie7eGOD5QHsXrUVe/Ioys8NUQ0554P/Y9wjcN+30PYPKPLkRqsekSHFXfdwTyLe2lTLmxtrAWuV6QSzU7Vh78QiCBYChwIzXH/LgMfs1+5P+XJgM/Bi2D02Yw34JzkFIlIIzAOW9rPtGU+RJzfkB5hONLa2E+jojDhTdM8iOzoNK7Y18NJHe/jX+zs57w9v8Y8Vn4TUr/W3Mqwssg565thy6vwB1lc18cSy7RHrKL3z2vpqvvOXFfzyuY+BrkmIg9unpbYpEDw/rsKLJzeHQypjM/4b7PIGHlth7R04ewhnHRFRS9yNXJeux/ltNDQHOPe+tzjvD29Rta+FfS6LsnT8/Qw0ve4RGGMagAZ3mYj4gTrb+scpKwa+DtxmwqZmxhgjIncCPxSRtcA64AagCXj0gHuRwfhK0tOpzIklFD6guMvcm8Y76vcH++kEKAveyx/goCHddcZrfvJ5ij1dj7A7HLLSNz5xhXyG7hv6FSWe4Mq01h8Inh8xqIhVN5/czUQ4Gjn2IP6DUz7FiEGWJ3JlWSFrbzmFgrzYbVcevWwW5//h7YiWTBuqm0LqNrW209reEfRQVroTz1hD5wBe4M9Rzt8GFAF3A0OAt4GTjTHdI1YpQXxeT1qqhpw296Qa6snSxE1dUyBi8nO3EFDiS7Gnu+9HVWMrbR2d7N3fFqriiVEI9HT/vt7DESLO/lmIIKhq6la/zh8IXqN0p18hJowxxxljvhdW9mdjTJ4xZmeUa4wx5mZjzAhjTKEx5lj3ikKJTLnX06tVTarx7pY6zrrX0vj1phpyeGDpFn7z0noA7lq8gRXb6rn5X6uZ+dMXaGxt73Ez0qHTtRBdvLaKR96OHCpB6aKmqZUfL/ywm5NYuJdwudeKe/WQbbEVy/cRiXgZ8DjP0Pcff5+OThOSt2NdhGioc37+ckiU1Nb2Dm586kOq1SMd0FhDKU86Bp47+//eDL72lXSf9ZcXezj3qIP43NRK/nDRTKC7OujMe5bywNItwR+4r6SAe79+BF881LJEvvXMQ4N1H/3WLIAQJ7WH39rKPYtDI18q3Xnl42oefmsrr67vOca/oxq6xQ4/Eel7jYX/OnESX5g+nK/EuB8QjTKXn8Ha3ftCfiPr93RfEQBc8uC7wdcvrqnioTe38vP/fHRA7cgUdG2d4jiqIWNMxFguqU6kmWNOjnDrWYcFj0cPKQpJSxiJcq+HU6YP5wsRcjfMnVjB1BFloV7H/gC1/ta0/dwGCuczi5RTwE251xOSUjKSqi4WhpUVcu8FR/brWjfu77SlrTPExHp9BNUQQHNrl6lpe6fVl9Y2TZkKuiJIeZwfYLrGG4pF9xvLoOIt6Pk+vhJPiHqgtqmVlrZOmgMaoronnLSP9c1tIWGkwwn/jvqrGkoEdf4ANU0BSgvyKC3Mo84fIDdHqCwrCOlTo+s3pAIgFBUEKU6XV2d09dCGqkbGzl/Eu1vqBqpZUfn9K31Xx3hye38MnRwN0QgP2R0pE1asnHD7EsbOX8TTqyJud6UN//239/nRPz/gy3e/wQNvbI5Yx50pzokpNNZX3K1euBVRKiWFueyhZdQ0tVJe4gkKqCHFHoaXFTJhWJf/iTGGpRtrGDt/Edf9PTxuZnajgiDFCcZ56WFAc9IA/vv95A9cv3phXfD1P6/oWxipcRVeHr706G7lP/vKdA4bHT3kAFgmqc6g39LWEVwJ9MfiapO9X/HQ0vTebF65vYHlW+t5f3sDqz7ZG7GOW1AOLS3g0ctm8cR35nSr5zYDPmXacIaVdg8dMtA8fdUxwdeba/z4vJ7g3kVFiYcFZx7KjadN4cFLrGdqXIWXf4b5qGhSIwvdI0hx4pnjdyBw9MinHTaCT48Z0qdrbzp9KvMmdQ8n8vVZB/d6ra/EQ1NrOy1tHSGDf7qG54gHdf5AjzkEIFRQlns9zJ1QEbGeWzV0lh0SItlMHzUo+Hr9niY+a4cbAau900Za5ycOK+X8WWN47sPd3UKVpKNpdiJQQZDiOD/Ad7bUsb6qiWMPGcrUkWWAlblr2Zb6YFiAdOdAHH7cKjS3uW1NHwWo2xegX+CKAAAgAElEQVSyI41DVnR0GuqbAzhdiORd+58PdrFye5evaE97NW7VUH83ihOJ48XuFgRufF4P9c0BwtItp51pdqJQQZDiOD/A+17dBMAvnl3LlltPBeA3L67nLleIXbdVR7Io9uTSHOjgtMNizyZ/9szRLNtaH9RRDy7ODyaaOXlqeHiqyLhzIddG2CuIFfemfG4ah610CwHo/jm8t62e7z6yIqSsood4QU68of1tHT3WSya+ki5BEJ7Qxuf10Gm6561IN9PsRKGCIMVx/wAdOjsNOTnC2t37QuqmwjJ3cFE+J0+t5JTpsQuCc44aw5lHjCbf3jRefsNJtHd2kitCToymn+4ImW41Wl9/6O76beHTxzQivN/hprSRUnv25uld7vXwScP+lFoRvP3DE5m1wAptXVqYH0xxGd7GclswbHSFn/ivEyfxm5fWsz/Q0efAd5mGbhanAeEWM27HKTfJXuYaY6jxBxhW1veNxHyX5VBujlCQl0tebk4wNk1vOINYbVNrcNZXVpgXMa1iTzjCtKwwL62DlYX3O9yUNtLqsbcB3lfiwZObQ0lB6swf3W0W6VoZRjN33V7X5a8yws6VodntVBCkBeFjYbSZ/4ptDaza0RDx3EDgD3QQaI8ccTTROCuC//fE+1Q3tuLJzeFgn5c6f4B7l2zkjQ01/O+/V/P/Hl/J3J+/FNUvwxn8D6kspc4foLPT8JN/rwmZSaYqm6qb+Mm/19DZaSKuhK7+63t8sGMv1/9jVUh2tzz7AetN5WNZ5UROUp8s3BOIHOl6DsL7EjFlqr1KuO7JVd1W1/HEGMPPFq2JGPoiVUgd0a7EjPMj73TpgPNzhbYOw1fvfZN1P/tCctplD6LJUB2UumapSzfW4iuxBq3apgC/eHZtt/qPvLWVbx87oVu5Y2U0qbKUZVvr2Vzr509vbGZoaQHfPS5yToRU4bnVe/jTG5u5dN64iILgpbVVvLS2Cgj9jh69bDZ/W7a9R4cysKyFevMATwbf/MxYHntnO+cebWUyPP3wkRzlSoADof2tKCngiuMmBMuWbqzl4j+9y1s/PDEh7atubOUPr22mtDA/5nDdA40KgjTixtOm8pOn1wRVQPXNXT/2rx45mr++s51AEvXazhK7r4nM44F7lvpJw35GDS6i3Ovh/e2RV0jtnZEtgpzV1iTbEcmJW5NstVssOG2sbWrt1Vqqzh9gcHE+K288GYCjx5X3WB/gtMNGHngjE8BNp0/jptOnBY9/d96nu9VxJ7O55JixXHLMOLa44lsl0p/A+S5S+RlS1VAaManSGpwixWBPBWp7yEEwkDQ0W2GSLZPBWLKrdlHXFKDYk8uoIdbseEOVtZxPtc86EsHEMf4Adf5WhhTnU5SfG3Wmn0phIhJNfm5OMClORYR8GInE/b2kKioI0gDHb2C8na7xhoUfUtvUGmIdM6bcG3z96NvbBraBNs4DnwpWJUOKPT1GyHTvEezd38bY+YsYO38Ru/e1BIUIwO3PW57SqfwjdghOEJoCVr7okgIqywqYMqIsYv0oi6KMJTy9pnvTO5HbHrWunAmvra9m7PxF/Pff3mfs/EW0p4hlmgqCNOCOc2bw0CVHh8zsPt7TSGNrO2fMGMlz13yWy+Z1Jfz+4+ubktHM4ECUDNUQwDNXzwu+zsuRHgWSW4i6s3NtrLZCFYRfm14rAks1VO718LvzjuCm06dGrN9Xi6p0J2hRZD+fbnViIn0H3XGvnBAXTy7fAUBDFAvAgUYFQRpQVpjPZw8JDb3gDF5Hj/MxeXgpebk5HDHGjseTpJlebVMrhfk5Scsc5qycHMqLIwuC3BwJMRlsbOn6MW6sbrJXBKGriVTW7zoE9wj89orA6+HQ0YM4qLx7EDmwnqtsIlJCJIfOBEoCR2Uaad8mVUyUVRCkKY+/ayVqjzTrdW98NTQHWLGtnu11zQk3X7MGn+TuD7jJy4283p80rIRaf4B1expZsa2e3768Pngu0N6Jr6SAsqJQYbZzbws7GwbOYmbdnkZ21DfHXN8YE0E1lHwVXSrhqAojqQwbW9rZWpuYnNfO91LfHGDJuuqQc6+sq0rIe/YVFQRpxsF2iOBlW+uBUDWMYz7n1n1+48/vcuY9S5l322JO/vWrCW1bbQoNPqceNiKqqd6EYSXU+QOc/OtXOfOepbyxoTbkvBWzprsQmXvrywlpaySuevQ9FjwTe/as5kAHrbaTWFVjK/XNgRBP4Uj7BBfPHXvA7UwnpgwvZfSQIrwuL+J5k7qC7B37yyUJeV/HJLkjgn/Hgme6mzYnAzUfTTNevvY4JvzwmeCxe5n7tZkH8cKaPWyr7ZpJRjOfTASpMAvd/PMv0mm64gRtWvBF3tvewFn3LuX4yUO5/xtHccuiNT0uyZ1V1sIrP8MZd78xIO0OZ/e+ll6T8bhx92dTTRPGhD4bz1xthWzuNFbeYEN3R8VM54LZB/P1WQeHCPmHLjmaf6/axdV/fS9h79ub+qelrSOmBE6JRFcEaUZ4ILRwVUxFSUGPFi4dCTQVqW1qTbpqSERCPqOcHGGoSyWQkyNUlBRE9Cx2LnPHtE8GbR2d7N3f1qcNamfPY1BRfjCMglsoi0jws8nJsf6nkofwQCAi3UKWiEjUvaR4UecP9JhYKRUMEVQQpDnhumwn3G5nlAG/oTkxD52jo072iiASjpVItDg0Do7TkS+4qdhdqF335Pu0JDiZieMoGKvJ6lubarnxqdUAHFLZ5f2cCma86YB74vBqmA7/QHljQw2bavxBB8VIXPPYSj7albgQF7GggiAN+fZnxwdfh8/qyr0eOjpN9MB0CZp9ODrqVBx8vJ5cLj1mHKdMHw5EHiB/cMqnutmZF3lyuWzeOB69bFaw3hPLdvDy2sRu8DmqhMaWdlrbexc65973Fh/YGcgmufZFkr06SxeOPLgrgdJFf3onrvf++h/fBkK/l698ehTQlRr0nS11XPLAu3F9376igiANuerESVHPucMxRyJR5mrO8jYVvVVFhB+fNjWYMS1c5XPx3LF897gJwc/Ovar50alTmTuhghtP67LFT6SpIYSqCur9fbMzd888U3F1lop48nKi+lrEi1GDuyLy/vqcGWy59VT+dPFRwbJk5xJRQZCGeHuIne7MAuv8gYizyUTpIx3npHQYfKLF3Xc+u0gzaXe/9gcSqxpyC/G+hkgePcSyKhMJja+jJJe83O5DbSqtnlUQpCE9bfI5D9fXfv8mk294ttv5l9dWMXb+Ipbb5qcHyq69+xk7fxEPLN1iv3/qqyPCf4BjbIeryrJCSgvzIiYpcV+T6HATbue1U3/7ep+uddo5uCg/rTOsDTROboKBvH9ZYdf+Xq0/wFMrPwkeb6hqZPIN/2FzTWJ8G8Lps/moiFwPLADuNsZ8z1V+CHArcALgAdYCXzfGfGSfLwBuB84DioCXgCuMMTsOtBPZyNNXHcOQCDOKSJYuVxw3gRGDi7jxqQ/553vWx714bVWIbrS/fLzbclJ7auVOIDVVQ+EMKsrn/m/MpL3TkJcjHDd5GADfOW48px8eObOae5WQaCuP8Pu7M4v1RnCju4c4S0p3Pj9tOJOGlbC5xt+nz7s3CvJyGDW4iC8dPpJJw0opLXTHNwp9j8fe2c6XZ1j7Bx/vbqK1vZN1exqDewmJpE8rAhGZDVwOrAorHwe8AWzGEgTTgRsAdzaPO4GzsATBPKAMeFpEsjtHXD+ZPmpQxKiSkYTDCZ8axoWzD2ZwUX7cA421d4TeMB1UQwAnTqnk89OGc+KUyuDMeVhpYXAfIRx3vxIdFqDWH8DjUiU096CKCtctO+1MJbVDOiAinD1zNO2dJmrSor7SHGintb2Tr84cjYgwdWRZ1HAf4dS5AtUNBDGvCERkEPAIcAlwU9jpnwHPG2OudZVtCrv2UuCbxpgX7LILga3A54Dn+tV6pRv5EXSRjg1zuSsss4lDQKLtdc3c/vzHweNkxhlKNG59e6JTG9Y2tTLGV8yGKicXQgBvlPSQ9WHmwCUFeXhyc9JiZZZqOGrNHy/8kNMPH8mJUyr7fI/VO/dSVpjPhuqm4Go51u/izU21+FvbWba1fsBzGPTlV3sf8KQxZrGIBAWBiOQApwO3isizwJHAFuB2Y8zjdrUjgXzgeec6Y8x2EfkImEsEQSAil2OtPhgzZkxf+qSEUWnrJ936xsaWA5/1zLttcchxJpsrOknRYWBUQxUlHjbYVqq1fkswRMK9OrlojuU1O21UGdNGRg49rUTHGbAXrtzJwpU7+eDmkyntY2C+SHs6Pe2bHT95KIs/7vJdmHaTNRTOHm8lChqo8OcxqYZE5DJgIpa6J5xhQAnwQ6yB/iTgr8AjInKqXWc40AHUhF27xz7XDWPMfcaYmcaYmUOHDo1URYmB288+PBhl0r0GSMQDli5qof6y5dZTOfPTowZENeTzFrDwys8APQse59wT357DT748HYB/XvEZvndCdBNjJTLhz29fTXdjva+bP3/zaLbcemq3cmfSljKqIRGZjLU5fIwxJtIn4wiTp4wxd9ivV4rITOB7wKK4tFTpF8UuC5jSgjz22SuBugQMZtmgly73egZkReBOjtOT0HbUVNnw2Sea8M+wp5VYJKJ5nPdHTbdn38DuEcSyIpgDVACrRaRdRNqBY4Er7Ne1QDuwJuy6jwBHp7MbyLXv46bSPqckCLcFoTuw1Zubalm1I74B6bJhMCov8bC/rYOx8xdR1dgS9/v/+/2dNDS34SvpSo4TbQWy8L1P+K/HVgLpYa2V6nTPQdG3QTjaoH0gv4veck/Hi1gEwULgUGCG628Z8Jj9OgC8C0wOu+4QrM1ggOVAG5baCAARGQ1MAZb2v/lKJB6/fDbnHT2Gc486KGgaCfDAN48mN0eC+u4L7++/O32bK8WeYyNdkQUmixWuweK3L63voWb/uMqOgunzeij25FKYnxO0IAnnmsdXBl/3FNRMiY0iTy6nHdZlPtzX2Xi0+iVRNvrd/P27c6PcM0U2i40xDUDI1FFE/ECdMeZD+/g24AkReQ14GTgeOBc4w77HXhG5H7hNRKqwVhF3YJmhvhi/7igAs8b7mDXe16186sgyNi74Ilc+soJFH+yKKY5NNBxrlVu+PA0R4YaFH2bHisDVx0RGci33FiAi+Lw9R5N1CI+qqfSPu84/gtu+2s7UG5/r8z5atPqx+CRE8+mp8wfi6tcQjbjY+hljFtpWPj8EfgOsBy4yxrj3B67BUiE9TpdD2UXGmMT66ytRaWnrmtUbYzAmtgHFGEPVPkc3XRBUP2WDICgpHBjzWGfVVu71pEw6w2yh2JNHYX5On003E2Hq2dZh2NfSnvAVX79CTBhjjnN7FdtlDxhjDjHGFBljDjPG/DXsfKsx5ipjjM8YU2yMOd0Ys/1AGq/0j0mV3UPi3rNkI+N/+ExMIZZvf/5jTvudZSbnK/Ew3FYNDS9LrJt+KuAWdp0JjBPmDoAXSeVgEhz4LtvxeQviphqKlYK80OHYyUY4EBvGmen9o/TI946fyENvbg2ZZfzp9c2AFTzOCVwWjf97JegriM/rYeKwEh685GiOmRhuC5B5HFJZyr1fP4LvPrIiaqjv/uIEs/vMRB9H2B7O5V4P6/c0dau7b79l/TVhqDckiqUSH3wlnn6phvJyhAVfOZRpo8ooyMvtNrj3xGvXHc/za/YwdWQZH+3ax5zxPt7eXMeQ4sTv/6ggyELycnP40uEj+fuK7mGe6vyBXgWBWzdebuf3PfaQ7PH1+MKhI5g1rjzuHsbO/U4/bGSwzOf1UNPU2k1PXGPXveqESRzsS3wsmmyj3P7c+0JtUyvlXg9fO+qgfr3nsLJCLph9MEBwIjB+aPSENvFEo49mKT6vh8aWdu56eT2L11YFZz8/W/QRe/ZZZpEdnYZnP9wdooZ4cc2ekPsMztJQx72lBO0PwZwOLusrX0kBre2dwXhDG6oaWbqxhp/bie0z3YkvWfi8BX32tbFydqen5ZyuCLIUJ33j7c+vCyl/e3Mdp/72NZbdcBKvb6jhO39Zzt+/O4cjDy6npa2Dbz20LKR+toY6ToRjmSNY3PsQzus6vxVv6HN3vNqtHUr8cVRDfbHYsTzC0/P70BVBltLTA+s4sTgrg6oIXo6TK0sjusZnC+VeDw3NbSH+FAeKYx3k/m6c19HUFJkc3ymZlHs9tLZ34u9DEqLapkDaCmYVBFlKLNmrnIGpxh8IOQa1W3dUMuHRPw8Ex3movCTyiiAS6TrwpDrBz70P6iEnNEg6ooIgS/HEYM0QjIlu/xh++M8PgucK87P70XGnBO0vv39lI/9630ro0xxoZ8EzawErJpSD46196YPLuPLRFd3uEcv3qPQdJ8HTLYvCI+dEprW9g6bW9oiJodIBfYqylENHDWJmmDfjV48cDUCePdt3dNZ1fstq5YNP9gbr3nnOjAFqaWrSnxljOD//z1qutkNKPLe6K+SWWyftnmEuWrUr5Porj5/Q7/dWesYJHf1CmHFENOqC+zvpqapTQZCl5OXm8KQrvsk7PzqR288+nKtPmEiHMXR0mhDVUKMra9P7N52c9SaLjmqopp8rgnCHsGjOacUR8icDzBpXzv98/lP9em+ld3whjoO9O+85vxVVDSlpTXlxV65bY6zNyarGLtVQdWPXZmXZAIVZSGV8wRVB1+fS0Wloaeugpa2j18HDvQnZ0tZBY0tk57REx5hRIuM2y43FcdBZPaerakh/0VlOZVkBe/a1kpfbFdsGYNaCl4J16vwBTvzVK8FjHZws/wmR0D2CI3/6Ag12KtATPzWM+3vw+HWrlD7142eDr2Pdg582clAfW6z0BXfK1Vp/IGIucDd1aZ4XQlcEWc6iq+fxzNXzgseRzErdHrS//OphA9KuVCc3RxhS7AlRDTlCAOCltVU9Xl8TxSv5retP7Fa2+L+PY2ippXueM97HY5fPZv4XVC2UaJzPOJZgcl2mv7pHoKQhFSUFTHXlt43kGelOjjE3C+IJxYrP6+n3ZnG064ZFCNw3rsLL2fZG/twJPmaP96m10ADw2UlW2JRYLMOcOENlRempZNGnSQmht6VtunpOJgLHu7iz03DPkg0xXbN8ax3b65oHLAWh0n+cfYKeQok8v3o3+1raePjNrQyx426lIyoIlBDCIx2eGxZAy53uMtvxlXio8bfyt+Xbue3Zj2O65spH3uPOF9dHVA1NH1UW4QqLU6YPB+CEKcOi1lHii+N0GS0fxCcN+7n84eWccdcbNLW2hxhUpBvpuY5REkZebg6/v/BIvv3wcg4dNYjTDhvJY+9aaSMe/dasJLcutbBi1teye2/XAHDbWYexe18Ld7ywjkB7Z4gKp7PTUN3USlVjC0OK8ynKz+WjW06J6b0OGz04q0N6JANPXg5lhXlR00VW2SFYNtX4B7JZCUFXBEo3IgU9g9DQB0pXvKEOlxNAQX5O8DMLDz+xr6WNjk5DnT+Q1uEIsglfD1FmM0m9p4JA6YbHNiUt8uSGbH5pgvRQHB3yb1/u2h/IzZHgPsqsBS8xdv4iFr73CdC16V7bFKDGH9AQ0mlAT6lCMymFqAoCpRvTRw3iW8eM41dnH86owUWcPLWSz0+rzIpUlH0hfEY/bWQZJ02t7FZ+zeMrga4ZpLUiaNWN9zTA10O48fCVwlNXfmYgmpQQdI9A6UZujnDDaVODx/ddNDOJrUldwm3GF9n+GNGSkzi65kBHJ9tqm5lcGX1zWEkNfCUeVmxriHjOvXdw+OhBHH7Q4IFqVtzRFYGi9JNoOv5oM323P8a+lnZVDaUB5V4P9c2BbiFDWts7QqyE0n2/R1cEitJPhngj75lE20sJVzGk++CRDZR7C+joNOzd3xYSZmLyDc92q5fOqCBQlH4yrLSQRy+bxfa6Zo6f3GXf707aM3JQITv3trA/0NFNEOgeQepT4XIqcwRBpKx06RpszkFVQ4pyAMydUME5R42JGBoC4OoTJwFWvKaaplYKXH4FsWSJU5JLpAxx9a7XzveZ7qs7XREoSgJxNo4d34GJw0pYvXMfYG3KK6mNM8A/sWw7Oxv209LWwRPLtgfPTxhawppd+9JeEPR5RSAi14uIEZG7XGUP2GXuv7fCrisQkd+JSI2I+EXkXyIyOh6dUJRU4/TDRzJxWElXvJomSxCMHFwUrDNtpFoNpTpOqtAnl+/gmsdXMv8fH4RYEc0aX86w0gKmj0rvsOB9WhGIyGzgcmBVhNMvAhe6jsONb+8EvgycB9QCdwBPi8iRxpgOFCWD+N15nwZga60VfqDWH6CmKcCMgzRURDrRm/puyvAybjp92gC1JnHEvCIQkUHAI8AlQH2EKq3GmN2uv7qway8F/scY84IxZgWW0DgM+NwB9UBRUhhHZVDb1Ep9s3oTpxu9hfsuyM+Mbda+9OI+4EljzOIo548RkSoRWScifxARd5jEI4F84HmnwBizHfgImIuiZCglBXl48nLYXOOno9OkvZmhEkpeThYJAhG5DJgI3BClyrPARcCJwLXA0cDLIuI89cOBDqAm7Lo99rlI73m5iCwTkWXV1dWxNFNRUg4RK/bQuj2NgJqMpiNXHDehW9nZR47m0mPGcdLUyiS0KP70ukcgIpOBBcAxxpiIWZyNMY+5Dj8QkeXAVuBU4B/9aZgx5j6sVQgzZ87sORO4oqQw5V4P66uaAFQ1lIZ8a9547lmyMXi8ccEXM87iK5YVwRygAlgtIu0i0g4cC1xhH3db6xpjdgI7gEl20W4g176Pm0r7nKJkLOVeD40t7cHXSnoxOMxTPNOEAMQmCBYChwIzXH/LgMfs191C84lIBTAK2GUXLQfagJNcdUYDU4Cl/W++oqQ+Fa4gdOma3DybycnAgT+cXlVDxpgGICT8noj4gTpjzIciUiIiNwN/xxr4xwI/B6qAf9r32Csi9wO3iUgVXeajq7DMThUlY3GvAqLFJ1JSm5euPZb2DkNRhqZqjYdncQfWiuEiYDCWMFgMfM0Y0+iqdw3QDjwOFAEvARepD4GS6TiCoLQwj4K8zBxIMp0JQ0uS3YSE0i9BYIw5zvV6P/D5GK5pBa6y/xQla3ACkqnFkJKqZIYRrKKkMI7vgG4UK6mKCgJFSTCOAFBnMiVVUUGgKAnGUQ2le8x6JXNRQaAoCaZrRaCCQElNNB+BoiSY0sJ8rjtlMidNyYxwBErmoYJAUQaAK46bmOwmKEpUVDWkKIqS5aggUBRFyXJUECiKomQ5KggURVGyHBUEiqIoWY4KAkVRlCxHBYGiKEqWo4JAURQlyxFjUj8dsIhUY+VA7g8VQE0cm5MOaJ+zA+1zdnAgfT7YGDO0t0ppIQgOBBFZZoyZmex2DCTa5+xA+5wdDESfVTWkKIqS5aggUBRFyXKyQRDcl+wGJAHtc3agfc4OEt7njN8jUBRFUXomG1YEiqIoSg+oIFAURclyVBAoiqJkORktCETkChHZLCItIrJcROYlu039QUSuF5F3RWSfiFSLyL9FZHpYHRGRm0Vkp4jsF5ElIjItrM4QEXlYRPbafw+LyOCB7U3fsftvROQuV1lG9ldERojIg/b33CIia0TkWNf5jOq3iOSKyC2u3+lmEfmpiOS56qR1n0XksyLyLxH5xH6OLw47H5f+icihIvKKfY9PRORGEZGYGmmMycg/4BygDbgMmAL8DmgCxiS7bf3oy3PAN4HpwKHAP4HdQLmrzg+ARuAsu94TwE6g1FXnP8BqYI79txr4d7L710vfZwObgfeBuzK5v8BgYBPwEHA0MA44EZiSqf0GfgjUAacDY4EvAfXAjzOlz8AXgQXAV4Fm4OKw8wfcP6DMHhOesO/xVfue18bUxmR/SAn88N8G/hBWth74ebLbFoe+lQAdwOn2sQC7gB+56hTZD8K37eMpgAE+46pzjF02Odl9itLPQcBG4HhgiSMIMri/C4A3ejifcf0GngYeDCt7EHg6E/uMNRm9ON7fKfBdYB9Q5KpzA/AJtnVoT38ZqRoSEQ9wJPB82KnngbkD36K4U4ql1qu3j8cBw3H11xizH3iVrv7OwXoIl7ru8wbgJ3U/k/uAJ40xi8PKM7W/ZwBvi8jjIlIlIitF5Huu5X0m9vt14HgR+RSAiEwFTgCesc9nYp/dxKt/c4DX7GsdngNGYq20eiQjBQFWkKZcYE9Y+R6sDz3d+Q2wEnjTPnb61FN/hwPVxp4qANivq0jBz0RELgMmYs1qwsm4/tqMB67AUg99Hut7vhW40j6fif3+BfAwsEZE2rBUHg8aY+6xz2din93Eq3/Do9zD/R5RyeutgpJaiMgdWMvCY4wxHcluTyIQkclYapJjjDFtyW7PAJIDLDPGXG8fvycik7AEwV3RL0trzgEuAs7HEgIzgN+IyGZjzP1JbVkWkakrghosHXplWHkl1oZKWiIivwbOA04wxmxynXL61FN/dwND3VYE9uthpN5nMgdrVbdaRNpFpB04FrjCfl1r18uU/jrsAtaElX0EjLFfZ9r3DPBL4HZjzGPGmA+MMQ8DdwCOMMzEPruJV/92R7mH+z2ikpGCwBgTAJYDJ4WdOolQPVvaICK/oUsIrA07vRnryz7JVb8QmEdXf9/E2mSe47puDuAl9T6ThVjWUTNcf8uAx+zX68is/jq8AUwOKzuErlwcmfY9AxRjTdrcdNA1NmVin93Eq39vAvPsax1OwrI+2tJrK5K9i57A3flzgADwLaxd999gbbgcnOy29aMvd2NZBJyApe9z/kpcdX4A7AXOxDIfe4zIJmgf0GWC9gEpYmIXw2ewhO7moxnVX+AoLJPnH2Htj5xt9/HKTO038ACwAzgVa1PzK0A18KtM6TPWIO5MaJqBG+3XY+LVPywLu932tdPte+0j281H7Q/nCixp2Iq1QvhsstvUz36YKH83u+oIcDOWeqEFeAWYHnafIcBf7Adkn/16cLL7F+NnEC4IMrK/9ogkRdsAAAB9SURBVID4vt2ndcDVuMz/Mq3fWBZwd2KtevZjbZQvAAozpc/AcVF+vw/Es39Yq+hX7XvsAm4iBtNRY4xGH1UURcl2MnKPQFEURYkdFQSKoihZjgoCRVGULEcFgaIoSpajgkBRFCXLUUGgKIqS5aggUBRFyXJUECiKomQ5/x+F8c9p2mFs2gAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 方法2 使用numpy的向量操作\r\n",
    "\r\n",
    "import numpy as np\r\n",
    "\r\n",
    "np.random.seed(10)\r\n",
    "row,col = int(1e3),3\r\n",
    "dice = np.random.randint(1,7,(row,col ))\r\n",
    "\r\n",
    "#---------- 计算6666 ---------------------------\r\n",
    "dice = np.column_stack((dice, dice.sum(axis=1)))\r\n",
    "point = dice[:,3]# 取点数之和列（第4列)\r\n",
    "print('投掷出666的次数:', (point == 18).sum())  \t# 出现666的次数\r\n",
    "print('666出现在第x次', np.where(point == 18)) \t# 出现在第x次\r\n",
    "\r\n",
    "#----------计算全相等--------------------------------\r\n",
    "# 找出三次投掷点数一样的行，得到一个布尔数组，注意 np.logical_and 函数\r\n",
    "condition = np.logical_and(dice[:, 0] == dice[:, 1], dice[:, 1] == dice[:, 2])\r\n",
    "print('\\n三次投掷点数均相同的次数:', condition.sum())  \t# 计算True的次数\r\n",
    "# print('投掷情况为:', dice[condition])  \t\t# 显示True对应的行\r\n",
    "\r\n",
    "# -------------- 计算筹码变化 -------------------------\r\n",
    "money = 500\t# 初始筹码数\r\n",
    "bep = 10\t\t# >10赢，<=10输\r\n",
    "print('\\n初始筹码:', money, ', 盈亏点:', bep, ', 每次输赢1')\r\n",
    "# 只需简单统计> bep和<= bep的次数之和，即可计算最后的筹码数\r\n",
    "total = money + (point > bep).sum() - (point <= bep).sum()\r\n",
    "print('最后筹码:', total)\r\n",
    "# 计算投掷过程中曾经达到的最高筹码数、最低筹码数\r\n",
    "# np.where判断每次输赢，返回1,-1构成的每次盈亏列，添加到末尾列，变为1000x5\r\n",
    "dice = np.column_stack((dice, np.where(dice[:, 3] > bep, 1, -1)))\r\n",
    "# np.cumsum累加每次盈亏列，得到累积盈亏列，添加到末尾列，变为1000x6\r\n",
    "dice = np.column_stack((dice, np.cumsum(dice[:, 4])))\r\n",
    "\r\n",
    "# 累积盈亏列加初始筹码数，得到当前筹码列，添加到末尾列，变为1000x7\r\n",
    "dice = np.column_stack((dice, dice[:, 5] + money))\r\n",
    "s = '曾经的最高筹码数{}, 出现在第{}次'\r\n",
    "print(s.format(np.max(dice[:, 6]), np.argmax(dice[:, 6])))\r\n",
    "s = '曾经的最低筹码数{}, 出现在第{}次'\r\n",
    "print(s.format(np.min(dice[:, 6]), np.argmin(dice[:, 6])))\r\n",
    "\r\n",
    "\r\n",
    "\r\n",
    "np.set_printoptions(threshold=10000)  # 输出数据个数的门槛\r\n",
    "# #筹码变化图\r\n",
    "import matplotlib.pyplot as plt\t\t# 引入绘图库\r\n",
    "# plt.rcParams['font.sans-serif'] = ['SimHei']\t# 指定中文黑体字体\r\n",
    "plt.xticks(fontsize=14)\t\t# 设x轴文字大小\r\n",
    "plt.yticks(fontsize=14)\t\t# 设y轴文字大小\r\n",
    "plt.title('筹码变化', fontsize=16) # 设标题文字\r\n",
    "plt.plot(dice[:, -1]) \t\t\t# 画筹码变化折线图\r\n",
    "\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**作业练习：**\n",
    "\n",
    "构造一个50*4的成绩数组，第0列代表学号，后续三维代表三门成绩（20-100之间，随机数整数表示）。\n",
    "\n",
    "给出常规的成绩统计数据和排序数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#-------------coding--------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**作业练习 实现简单的线性回归**\n",
    "\n",
    "回归分析是一种预测性的建模技术，它研究的是因变量（目标）和自变量（预测器）之间的关系。这种技术通常用于预测分析，时间序列模型以及发现变量之间的因果关系。通常使用曲线/线来拟合数据点，目标是使曲线到数据点的距离差异最小。\n",
    "\n",
    "线性回归是回归问题中的一种，线性回归假设目标值与特征之间线性相关，即满足一个多元一次方程。通过构建损失函数，来求解损失函数最小时的参数w和b。\n",
    "\n",
    "[线性回归分析](https://www.cnblogs.com/geo-will/p/10468253.html)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "**作业练习3 用Numpy实现粒子群算法（PSO）**\n",
    "\n",
    "粒子群算法通过设计一种无质量的粒子来模拟鸟群中的鸟，粒子仅具有两个属性：速度和位置，速度代表移动的快慢，位置代表移动的方向。每个粒子在搜索空间中单独的搜寻最优解，并将其记为当前个体极值，并将个体极值与整个粒子群里的其他粒子共享，找到最优的那个个体极值作为整个粒子群的当前全局最优解，粒子群中的所有粒子根据自己找到的当前个体极值和整个粒子群共享的当前全局最优解来调整自己的速度和位置。下面的动图很形象地展示了PSO算法的过程： \n",
    "\n",
    "![](images/20180803102329735.gif)\n",
    "\n",
    " [PSO的详细讲解](https://blog.csdn.net/daaikuaichuan/article/details/81382794)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1.1773885661863458e-166, 1.1455661386737751e-166, 9.961783406138935e-167, 7.864540933827453e-167, 7.864540933827453e-167]\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 粒子群算法的基本功能。\r\n",
    "import numpy as np\r\n",
    "import matplotlib.pylab as plt\r\n",
    "\r\n",
    "np.random.seed(1)\r\n",
    "def fitnessEvalute(x):\r\n",
    "    return np.sum(x**2,axis=1)\r\n",
    "\r\n",
    "# 初始化过程\r\n",
    "lb,ub = -10,10\r\n",
    "popsize,dim = 30,10\r\n",
    "pop = np.random.uniform(lb,ub,(popsize,dim))  # generate population\r\n",
    "individualBest = pop.copy() # location and the individual best\r\n",
    "velocity = pop.copy() # velocity of every particle\r\n",
    "position = pop.copy()\r\n",
    "fit = fitnessEvalute(pop)\r\n",
    "globalBest = pop[0]\r\n",
    "\r\n",
    "# 初始化过程结束，进入迭代过程\r\n",
    "fitBest = []\r\n",
    "maxIter =10000 # maximum iteration\r\n",
    "omiga,c1,c2 = 0.7,1.4,1.4 # 对应于w，c1,c2的参数表\r\n",
    "for i in range(maxIter):\r\n",
    "    r1 = np.random.random(); r2 = np.random.random() # 生成两个随机数\r\n",
    "    # 更新速度和位置\r\n",
    "    velocity = velocity * omiga + r1*c1*(globalBest - pop) + r2*c2*(individualBest - pop) # \r\n",
    "    pop += velocity \r\n",
    "    #合法性检查 \r\n",
    "    beyondLb = np.random.uniform(lb,ub);  beyondUb = np.random.uniform(lb,ub)  # 越界处理机制\r\n",
    "    temp = np.where(pop<lb,beyondLb,pop)\r\n",
    "    pop = np.where(temp>ub,beyondUb,temp)\r\n",
    "    # #评价\r\n",
    "    fitCurrent = fitnessEvalute(pop)\r\n",
    "    #更新历史最优解和个体最优解\r\n",
    "    ind = fitCurrent<fit\r\n",
    "    individualBest = np.vstack((pop[ind],individualBest[~ind])).copy() # 更新个体最优历史\r\n",
    "    globalBest = pop[np.argmin(fitCurrent)] #群体最优值\r\n",
    "    fit = np.hstack((fitCurrent[ind],fit[~ind])).copy() # 更新适应值\r\n",
    "    fitBest.append(np.min(fit))  # 记录每一代最优值。\r\n",
    "\r\n",
    "print(fitBest[-5:]) # 输出最后五个值\r\n",
    "# 汇出函数图像。为了便于观察，取适应值的对数。\r\n",
    "plt.plot(np.log(fitBest))\r\n",
    "plt.show()\r\n",
    "   "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
